/* Copyright 2009-2010 Yorba Foundation * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ using Logging; namespace Model { public class ClipImporter : MultiFileProgressInterface, Object { enum ImportState { FETCHING, IMPORTING, CANCELLED } string import_directory; ImportState import_state; bool import_done; bool all_done; ClipFetcher our_fetcher; Gst.Pad video_pad; Gst.Pad audio_pad; Gst.Pipeline pipeline = null; Gst.Element filesink; Gst.Element video_convert; Gst.Element audio_convert; Gst.Element mux; Gst.Bin video_decoder; Gst.Bin audio_decoder; int current_file_importing = 0; int64 current_time; int64 total_time; int64 previous_time; Gee.ArrayList filenames = new Gee.ArrayList(); Gee.ArrayList queued_fetchers = new Gee.ArrayList(); Gee.ArrayList queued_filenames = new Gee.ArrayList(); Gee.ArrayList no_import_formats = new Gee.ArrayList(); public signal void clip_complete(ClipFile f); public signal void importing_started(int num_clips); public signal void error_occurred(string error); public ClipImporter() { import_directory = GLib.Environment.get_home_dir(); import_directory += "/.lombard_fillmore_import/"; GLib.DirUtils.create(import_directory, 0777); no_import_formats.add("YUY2"); no_import_formats.add("Y41B"); import_state = ImportState.FETCHING; } public void add_filename(string filename) { filenames.add(filename); } bool on_timer_callback() { int64 time; Gst.Format format = Gst.Format.TIME; if (all_done) return false; if (pipeline.query_position(ref format, out time) && format == Gst.Format.TIME) { if (time > previous_time) current_time += time - previous_time; previous_time = time; if (current_time >= total_time) { fraction_updated(1.0); return false; } else fraction_updated(current_time / (double)total_time); } return true; } void start_import() { import_state = ImportState.IMPORTING; current_file_importing = 0; importing_started(queued_fetchers.size); Timeout.add(50, on_timer_callback); } void cancel() { all_done = true; import_state = ImportState.CANCELLED; if (pipeline != null) { pipeline.set_state(Gst.State.NULL); } } public void start() throws Error { process_curr_file(); } void process_curr_file() throws Error { if (import_state == ImportState.FETCHING) { if (current_file_importing == filenames.size) { if (queued_fetchers.size == 0) done(); else start_import(); } else { emit(this, Facility.IMPORT, Level.VERBOSE, "fetching %s".printf(filenames[current_file_importing])); our_fetcher = new Model.ClipFetcher(filenames[current_file_importing]); our_fetcher.ready.connect(on_fetcher_ready); } } if (import_state == ImportState.IMPORTING) { if (current_file_importing == queued_fetchers.size) { fraction_updated(1.0); all_done = true; } else do_import(queued_fetchers[current_file_importing]); } } // TODO: Rework this void complete() { all_done = true; } void do_import_complete() throws Error{ if (import_state == ImportState.IMPORTING) { our_fetcher.clipfile.filename = append_extension( queued_filenames[current_file_importing], "mov"); clip_complete(our_fetcher.clipfile); } else total_time += our_fetcher.clipfile.length; current_file_importing++; if (current_file_importing <= filenames.size) process_curr_file(); else warning ("do_import_complete: current_file_importing out of bounds! %d %d".printf( current_file_importing, filenames.size)); } bool need_to_import(Fetcher f) { //for now, use the clip as is return false; /* if (f.clipfile.is_of_type(MediaType.VIDEO)) { uint32 format; if (f.clipfile.get_video_format(out format)) { foreach (string s in no_import_formats) { if (format == *(uint32*)s) { return false; } } return true; } } return false; */ } void on_fetcher_ready(Fetcher f) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_fetcher_ready"); try { if (f.error_string != null) { error_occurred(f.error_string); do_import_complete(); return; } if (need_to_import(f)) { string checksum; if (md5_checksum_on_file(f.clipfile.filename, out checksum)) { string base_filename = import_directory + isolate_filename(f.clipfile.filename); int index = 0; string new_filename = base_filename; while (true) { string existing_checksum; if (get_file_md5_checksum(new_filename, out existing_checksum)) { if (checksum == existing_checksum) { // Re-fetch this clip to get the correct caps filenames[current_file_importing] = append_extension(new_filename, "mov"); current_file_importing--; total_time -= f.clipfile.length; break; } index++; new_filename = base_filename + index.to_string(); } else { // Truly need to import save_file_md5_checksum(new_filename, checksum); queued_filenames.add(new_filename); queued_fetchers.add(f as ClipFetcher); break; } } } else error("Cannot get md5 checksum for file %s!", f.clipfile.filename); } else { clip_complete(f.clipfile); } do_import_complete(); } catch (Error e) { error_occurred(e.message); } } void do_import(ClipFetcher f) throws Error { file_updated(f.clipfile.filename, current_file_importing); previous_time = 0; our_fetcher = f; import_done = false; pipeline = new Gst.Pipeline("pipeline"); pipeline.set_auto_flush_bus(false); Gst.Bus bus = pipeline.get_bus(); bus.add_signal_watch(); bus.message["state-changed"] += on_state_changed; bus.message["eos"] += on_eos; bus.message["error"] += on_error; bus.message["warning"] += on_warning; mux = make_element("qtmux"); filesink = make_element("filesink"); filesink.set("location", append_extension(queued_filenames[current_file_importing], "mov")); pipeline.add_many(mux, filesink); if (f.clipfile.is_of_type(MediaType.VIDEO)) { video_convert = make_element("ffmpegcolorspace"); pipeline.add(video_convert); video_decoder = new SingleDecodeBin(Gst.Caps.from_string( "video/x-raw-yuv"), "videodecodebin", f.clipfile.filename); video_decoder.pad_added.connect(on_pad_added); pipeline.add(video_decoder); if (!video_convert.link(mux)) error("do_import: Cannot link video converter to mux!"); } if (f.clipfile.is_of_type(MediaType.AUDIO)) { audio_convert = make_element("audioconvert"); pipeline.add(audio_convert); // setup for importing h.264 and other int flavors. change to audio/x-raw-float // if you need to import ogg and other float flavors. see bug 2055 audio_decoder = new SingleDecodeBin( Gst.Caps.from_string("audio/x-raw-int"), "audiodecodebin", f.clipfile.filename); audio_decoder.pad_added.connect(on_pad_added); pipeline.add(audio_decoder); if (!audio_convert.link(mux)) error("do_import: Cannot link audio convert to mux!"); } if (!mux.link(filesink)) error("do_import: Cannot link mux to filesink!"); emit(this, Facility.IMPORT, Level.VERBOSE, "Starting import to %s...".printf(queued_filenames[current_file_importing])); pipeline.set_state(Gst.State.PLAYING); } void on_pad_added(Gst.Pad p) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_pad_added"); string str = p.caps.to_string(); Gst.Pad sink = null; if (str.has_prefix("video")) { video_pad = p; sink = video_convert.get_compatible_pad(p, p.caps); } else if (str.has_prefix("audio")) { audio_pad = p; sink = audio_convert.get_compatible_pad(p, p.caps); } else { //error_occurred here gives a segfault warning("Unrecognized prefix %s".printf(str)); return; } if (p.link(sink) != Gst.PadLinkReturn.OK) { error("Cannot link pad in importer!"); } } void on_error(Gst.Bus bus, Gst.Message message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_error"); Error e; string text; message.parse_error(out e, out text); warning("%s\n", text); error_occurred(text); } void on_warning(Gst.Bus bus, Gst.Message message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_warning"); Error e; string text; message.parse_warning(out e, out text); warning("%s", text); } void on_state_changed(Gst.Bus b, Gst.Message m) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_state_changed"); if (m.src != pipeline) return; Gst.State old_state; Gst.State new_state; Gst.State pending; m.parse_state_changed (out old_state, out new_state, out pending); if (old_state == new_state) return; emit(this, Facility.IMPORT, Level.VERBOSE, "Import State in %s".printf(new_state.to_string())); if (new_state == Gst.State.PAUSED) { if (!import_done) { if (video_pad != null) { our_fetcher.clipfile.video_caps = video_pad.caps; } if (audio_pad != null) { our_fetcher.clipfile.audio_caps = audio_pad.caps; } emit(this, Facility.IMPORT, Level.VERBOSE, "Got clipfile info for: %s".printf(our_fetcher.clipfile.filename)); } } else if (new_state == Gst.State.NULL) { if (import_state == ImportState.CANCELLED) { GLib.FileUtils.remove(append_extension(queued_filenames[current_file_importing], "mov")); GLib.FileUtils.remove(append_extension(queued_filenames[current_file_importing], "md5")); } else { if (import_done) try { do_import_complete(); } catch (Error e) { error_occurred(e.message); } } } } void on_eos() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_eos"); import_done = true; pipeline.set_state(Gst.State.NULL); } } public class LibraryImporter : Object { protected Project project; public ClipImporter importer; public signal void started(ClipImporter i, int num); public LibraryImporter(Project p) { project = p; importer = new ClipImporter(); importer.clip_complete.connect(on_clip_complete); importer.error_occurred.connect(on_error_occurred); importer.importing_started.connect(on_importer_started); } void on_importer_started(ClipImporter i, int num) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_importer_started"); started(i, num); } void on_error_occurred(string error) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_error_occurred"); emit(this, Facility.DEVELOPER_WARNINGS, Level.INFO, error); project.error_occurred("Error importing", "An error occurred importing this file."); } protected virtual void append_existing_clipfile(ClipFile f) { } protected virtual void on_clip_complete(ClipFile f) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_complete"); ClipFile cf = project.find_clipfile(f.filename); if (cf == null) { project.add_clipfile(f); } } public void add_file(string filename) throws Error { ClipFile cf = project.find_clipfile(filename); if (cf != null) append_existing_clipfile(cf); else importer.add_filename(filename); } public void start() throws Error { importer.start(); } } public class TimelineImporter : LibraryImporter { Track track; int64 time_to_add; bool both_tracks; public TimelineImporter(Track track, Project p, int64 time_to_add, bool both_tracks) { base(p); this.track = track; this.time_to_add = time_to_add; this.both_tracks = both_tracks; } void add_to_both(ClipFile clip_file) { if (both_tracks) { Track other_track; if (track is Model.VideoTrack) { other_track = project.find_audio_track(); } else { other_track = project.find_video_track(); } if (other_track != null) { project.add(other_track, clip_file, time_to_add); } } } protected override void append_existing_clipfile(ClipFile f) { project.undo_manager.start_transaction("Create Clip"); project.add(track, f, time_to_add); add_to_both(f); project.undo_manager.end_transaction("Create Clip"); } protected override void on_clip_complete(ClipFile f) { project.undo_manager.start_transaction("Create Clip"); base.on_clip_complete(f); project.add(track, f, time_to_add); add_to_both(f); project.undo_manager.end_transaction("Create Clip"); } } }