From d443a3c2509889533ca812c163056bace396b586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Wed, 14 Jun 2023 20:35:58 +0200 Subject: New upstream version 0.32.1 --- src/VideoSupport.vala | 1193 ------------------------------------------------- 1 file changed, 1193 deletions(-) delete mode 100644 src/VideoSupport.vala (limited to 'src/VideoSupport.vala') diff --git a/src/VideoSupport.vala b/src/VideoSupport.vala deleted file mode 100644 index ec827ea..0000000 --- a/src/VideoSupport.vala +++ /dev/null @@ -1,1193 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU LGPL (version 2.1 or later). - * See the COPYING file in this distribution. - */ - -public errordomain VideoError { - FILE, // there's a problem reading the video container file (doesn't exist, no read - // permission, etc.) - - CONTENTS, // we can read the container file but its contents are indecipherable (no codec, - // malformed data, etc.) -} - -public class VideoImportParams { - // IN: - public File file; - public ImportID import_id = ImportID(); - public string? md5; - public time_t exposure_time_override; - - // IN/OUT: - public Thumbnails? thumbnails; - - // OUT: - public VideoRow row = new VideoRow(); - - public VideoImportParams(File file, ImportID import_id, string? md5, - Thumbnails? thumbnails = null, time_t exposure_time_override = 0) { - this.file = file; - this.import_id = import_id; - this.md5 = md5; - this.thumbnails = thumbnails; - this.exposure_time_override = exposure_time_override; - } -} - -public class VideoReader { - private const double UNKNOWN_CLIP_DURATION = -1.0; - private const uint THUMBNAILER_TIMEOUT = 10000; // In milliseconds. - - // File extensions for video containers that pack only metadata as per the AVCHD spec - private const string[] METADATA_ONLY_FILE_EXTENSIONS = { "bdm", "bdmv", "cpi", "mpl" }; - - private double clip_duration = UNKNOWN_CLIP_DURATION; - private Gdk.Pixbuf preview_frame = null; - private File file = null; - private GLib.Pid thumbnailer_pid = 0; - public DateTime? timestamp { get; private set; default = null; } - - public VideoReader(File file) { - this.file = file; - } - - public static bool is_supported_video_file(File file) { - var mime_type = ContentType.guess(file.get_basename(), new uchar[0], null); - // special case: deep-check content-type of files ending with .ogg - if (mime_type == "audio/ogg" && file.has_uri_scheme("file")) { - try { - var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, - FileQueryInfoFlags.NONE); - var content_type = info.get_content_type(); - if (content_type != null && content_type.has_prefix ("video/")) { - return true; - } - } catch (Error error) { - debug("Failed to query content type: %s", error.message); - } - } - - return is_supported_video_filename(file.get_basename()); - } - - public static bool is_supported_video_filename(string filename) { - string mime_type; - mime_type = ContentType.guess(filename, new uchar[0], null); - // Guessed mp4 from filename has application/ as prefix, so check for mp4 in the end - if (mime_type.has_prefix ("video/") || mime_type.has_suffix("mp4")) { - string? extension = null; - string? name = null; - disassemble_filename(filename, out name, out extension); - - if (extension == null) - return true; - - foreach (string s in METADATA_ONLY_FILE_EXTENSIONS) { - if (utf8_ci_compare(s, extension) == 0) - return false; - } - - return true; - } else { - debug("Skipping %s, unsupported mime type %s", filename, mime_type); - return false; - } - } - - public static ImportResult prepare_for_import(VideoImportParams params) { -#if MEASURE_IMPORT - Timer total_time = new Timer(); -#endif - File file = params.file; - - FileInfo info = null; - try { - info = file.query_info(DirectoryMonitor.SUPPLIED_ATTRIBUTES, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); - } catch (Error err) { - return ImportResult.FILE_ERROR; - } - - if (info.get_file_type() != FileType.REGULAR) - return ImportResult.NOT_A_FILE; - - if (!is_supported_video_file(file)) { - message("Not importing %s: file is marked as a video file but doesn't have a" + - "supported extension", file.get_path()); - - return ImportResult.UNSUPPORTED_FORMAT; - } - - TimeVal timestamp = info.get_modification_time(); - - // make sure params has a valid md5 - assert(params.md5 != null); - - time_t exposure_time = params.exposure_time_override; - string title = ""; - string comment = ""; - - VideoReader reader = new VideoReader(file); - bool is_interpretable = true; - double clip_duration = 0.0; - Gdk.Pixbuf preview_frame = reader.read_preview_frame(); - try { - clip_duration = reader.read_clip_duration(); - } catch (VideoError err) { - if (err is VideoError.FILE) { - return ImportResult.FILE_ERROR; - } else if (err is VideoError.CONTENTS) { - is_interpretable = false; - clip_duration = 0.0; - } else { - error("can't prepare video for import: an unknown kind of video error occurred"); - } - } - - try { - VideoMetadata metadata = reader.read_metadata(); - MetadataDateTime? creation_date_time = metadata.get_creation_date_time(); - - if (creation_date_time != null && creation_date_time.get_timestamp() != 0) - exposure_time = creation_date_time.get_timestamp(); - - string? video_title = metadata.get_title(); - string? video_comment = metadata.get_comment(); - if (video_title != null) - title = video_title; - if (video_comment != null) - comment = video_comment; - } catch (Error err) { - warning("Unable to read video metadata: %s", err.message); - } - - if (exposure_time == 0) { - // Use time reported by Gstreamer, if available. - exposure_time = (time_t) (reader.timestamp != null ? - reader.timestamp.to_unix() : 0); - } - - params.row.video_id = VideoID(); - params.row.filepath = file.get_path(); - params.row.filesize = info.get_size(); - params.row.timestamp = timestamp.tv_sec; - params.row.width = preview_frame.width; - params.row.height = preview_frame.height; - params.row.clip_duration = clip_duration; - params.row.is_interpretable = is_interpretable; - params.row.exposure_time = exposure_time; - params.row.import_id = params.import_id; - params.row.event_id = EventID(); - params.row.md5 = params.md5; - params.row.time_created = 0; - params.row.title = title; - params.row.comment = comment; - params.row.backlinks = ""; - params.row.time_reimported = 0; - params.row.flags = 0; - - if (params.thumbnails != null) { - params.thumbnails = new Thumbnails(); - ThumbnailCache.generate_for_video_frame(params.thumbnails, preview_frame); - } - -#if MEASURE_IMPORT - debug("IMPORT: total time to import video = %lf", total_time.elapsed()); -#endif - return ImportResult.SUCCESS; - } - - private void read_internal() throws VideoError { - if (!does_file_exist()) - throw new VideoError.FILE("video file '%s' does not exist or is inaccessible".printf( - file.get_path())); - - try { - Gst.PbUtils.Discoverer d = new Gst.PbUtils.Discoverer((Gst.ClockTime) (Gst.SECOND * 5)); - Gst.PbUtils.DiscovererInfo info = d.discover_uri(file.get_uri()); - - clip_duration = ((double) info.get_duration()) / 1000000000.0; - - // Get creation time. - // TODO: Note that TAG_DATE can be changed to TAG_DATE_TIME in the future - // (and the corresponding output struct) in order to implement #2836. - Date? video_date = null; - if (info.get_tags() != null && info.get_tags().get_date(Gst.Tags.DATE, out video_date)) { - // possible for get_date() to return true and a null Date - if (video_date != null) { - timestamp = new DateTime.local(video_date.get_year(), video_date.get_month(), - video_date.get_day(), 0, 0, 0); - } - } - } catch (Error e) { - debug("Video read error: %s", e.message); - throw new VideoError.CONTENTS("GStreamer couldn't extract clip information: %s" - .printf(e.message)); - } - } - - // Used by thumbnailer() to kill the external process if need be. - private bool on_thumbnailer_timer() { - debug("Thumbnailer timer called"); - if (thumbnailer_pid != 0) { - debug("Killing thumbnailer process: %d", thumbnailer_pid); -#if VALA_0_40 - Posix.kill(thumbnailer_pid, Posix.Signal.KILL); -#else - Posix.kill(thumbnailer_pid, Posix.SIGKILL); -#endif - } - return false; // Don't call again. - } - - // Performs video thumbnailing. - // Note: not thread-safe if called from the same instance of the class. - private Gdk.Pixbuf? thumbnailer(string video_file) { - // Use Shotwell's thumbnailer, redirect output to stdout. - debug("Launching thumbnailer process: %s", AppDirs.get_thumbnailer_bin().get_path()); - string[] argv = {AppDirs.get_thumbnailer_bin().get_path(), video_file}; - int child_stdout; - try { - GLib.Process.spawn_async_with_pipes(null, argv, null, GLib.SpawnFlags.SEARCH_PATH | - GLib.SpawnFlags.DO_NOT_REAP_CHILD, null, out thumbnailer_pid, null, out child_stdout, - null); - debug("Spawned thumbnailer, child pid: %d", (int) thumbnailer_pid); - } catch (Error e) { - debug("Error spawning process: %s", e.message); - if (thumbnailer_pid != 0) - GLib.Process.close_pid(thumbnailer_pid); - return null; - } - - // Start timer. - Timeout.add(THUMBNAILER_TIMEOUT, on_thumbnailer_timer); - - // Read pixbuf from stream. - Gdk.Pixbuf? buf = null; - try { - GLib.UnixInputStream unix_input = new GLib.UnixInputStream(child_stdout, true); - buf = new Gdk.Pixbuf.from_stream(unix_input, null); - } catch (Error e) { - debug("Error creating pixbuf: %s", e.message); - buf = null; - } - - // Make sure process exited properly. - int child_status = 0; - int ret_waitpid = Posix.waitpid(thumbnailer_pid, out child_status, 0); - if (ret_waitpid < 0) { - debug("waitpid returned error code: %d", ret_waitpid); - buf = null; - } else if (0 != Process.exit_status(child_status)) { - debug("Thumbnailer exited with error code: %d", - Process.exit_status(child_status)); - buf = null; - } - - GLib.Process.close_pid(thumbnailer_pid); - thumbnailer_pid = 0; - return buf; - } - - private bool does_file_exist() { - return FileUtils.test(file.get_path(), FileTest.EXISTS | FileTest.IS_REGULAR); - } - - public Gdk.Pixbuf? read_preview_frame() { - if (preview_frame != null) - return preview_frame; - - if (!does_file_exist()) - return null; - - // Get preview frame from thumbnailer. - preview_frame = thumbnailer(file.get_path()); - if (null == preview_frame) - preview_frame = Resources.get_noninterpretable_badge_pixbuf(); - - return preview_frame; - } - - public double read_clip_duration() throws VideoError { - if (clip_duration == UNKNOWN_CLIP_DURATION) - read_internal(); - - return clip_duration; - } - - public VideoMetadata read_metadata() throws Error { - VideoMetadata metadata = new VideoMetadata(); - metadata.read_from_file(File.new_for_path(file.get_path())); - - return metadata; - } -} - -public class Video : VideoSource, Flaggable, Monitorable, Dateable { - public const string TYPENAME = "video"; - - public const uint64 FLAG_TRASH = 0x0000000000000001; - public const uint64 FLAG_OFFLINE = 0x0000000000000002; - public const uint64 FLAG_FLAGGED = 0x0000000000000004; - - public class InterpretableResults { - internal Video video; - internal bool update_interpretable = false; - internal bool is_interpretable = false; - internal Gdk.Pixbuf? new_thumbnail = null; - - public InterpretableResults(Video video) { - this.video = video; - } - - public void foreground_finish() { - if (update_interpretable) - video.set_is_interpretable(is_interpretable); - - if (new_thumbnail != null) { - try { - ThumbnailCache.replace(video, ThumbnailCache.Size.BIG, new_thumbnail); - ThumbnailCache.replace(video, ThumbnailCache.Size.MEDIUM, new_thumbnail); - - video.notify_thumbnail_altered(); - } catch (Error err) { - message("Unable to update video thumbnails for %s: %s", video.to_string(), - err.message); - } - } - } - } - - private static bool normal_regen_complete; - private static bool offline_regen_complete; - public static VideoSourceCollection global; - - private VideoRow backing_row; - - public Video(VideoRow row) { - this.backing_row = row; - - // normalize user text - this.backing_row.title = prep_title(this.backing_row.title); - - if (((row.flags & FLAG_TRASH) != 0) || ((row.flags & FLAG_OFFLINE) != 0)) - rehydrate_backlinks(global, row.backlinks); - } - - public static void init(ProgressMonitor? monitor = null) { - // Must initialize static variables here. - // TODO: set values at declaration time once the following Vala bug is fixed: - // https://bugzilla.gnome.org/show_bug.cgi?id=655594 - normal_regen_complete = false; - offline_regen_complete = false; - - // initialize GStreamer, but don't pass it our actual command line arguments -- we don't - // want our end users to be able to parameterize the GStreamer configuration - unowned string[] args = null; - Gst.init(ref args); - - var registry = Gst.Registry.@get (); - - /* Update our local registr to not include vaapi stuff. This is basically to - * work-around concurrent access to VAAPI/X11 which it doesn't like, cf - * https://bugzilla.gnome.org/show_bug.cgi?id=762416 - */ - - var features = registry.feature_filter ((f) => { - return f.get_name ().has_prefix ("vaapi"); - }, false); - - foreach (var feature in features) { - debug ("Removing registry feature %s", feature.get_name ()); - registry.remove_feature (feature); - } - - global = new VideoSourceCollection(); - - Gee.ArrayList all = VideoTable.get_instance().get_all(); - Gee.ArrayList