1 /* Copyright 2009-2010 Yorba Foundation
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later). See the COPYING file in this distribution.
11 public class ClipImporter : MultiFileProgressInterface, Object {
18 string import_directory;
20 ImportState import_state;
23 ClipFetcher our_fetcher;
28 Gst.Pipeline pipeline = null;
30 Gst.Element video_convert;
31 Gst.Element audio_convert;
34 Gst.Bin video_decoder;
35 Gst.Bin audio_decoder;
37 int current_file_importing = 0;
43 Gee.ArrayList<string> filenames = new Gee.ArrayList<string>();
44 Gee.ArrayList<ClipFetcher> queued_fetchers = new Gee.ArrayList<ClipFetcher>();
45 Gee.ArrayList<string> queued_filenames = new Gee.ArrayList<string>();
46 Gee.ArrayList<string> no_import_formats = new Gee.ArrayList<string>();
48 public signal void clip_complete(ClipFile f);
49 public signal void importing_started(int num_clips);
50 public signal void error_occurred(string error);
52 public ClipImporter() {
53 import_directory = GLib.Environment.get_home_dir();
54 import_directory += "/.lombard_fillmore_import/";
56 GLib.DirUtils.create(import_directory, 0777);
58 no_import_formats.add("YUY2");
59 no_import_formats.add("Y41B");
61 import_state = ImportState.FETCHING;
64 public void add_filename(string filename) {
65 filenames.add(filename);
68 bool on_timer_callback() {
70 Gst.Format format = Gst.Format.TIME;
75 if (pipeline.query_position(ref format, out time) &&
76 format == Gst.Format.TIME) {
77 if (time > previous_time)
78 current_time += time - previous_time;
80 if (current_time >= total_time) {
81 fraction_updated(1.0);
84 fraction_updated(current_time / (double)total_time);
90 import_state = ImportState.IMPORTING;
91 current_file_importing = 0;
92 importing_started(queued_fetchers.size);
93 Timeout.add(50, on_timer_callback);
98 import_state = ImportState.CANCELLED;
99 if (pipeline != null) {
100 pipeline.set_state(Gst.State.NULL);
104 public void start() throws Error {
108 void process_curr_file() throws Error {
109 if (import_state == ImportState.FETCHING) {
110 if (current_file_importing == filenames.size) {
111 if (queued_fetchers.size == 0)
116 emit(this, Facility.IMPORT, Level.VERBOSE,
117 "fetching %s".printf(filenames[current_file_importing]));
118 our_fetcher = new Model.ClipFetcher(filenames[current_file_importing]);
119 our_fetcher.ready.connect(on_fetcher_ready);
123 if (import_state == ImportState.IMPORTING) {
124 if (current_file_importing == queued_fetchers.size) {
125 fraction_updated(1.0);
128 do_import(queued_fetchers[current_file_importing]);
137 void do_import_complete() throws Error{
138 if (import_state == ImportState.IMPORTING) {
139 our_fetcher.clipfile.filename = append_extension(
140 queued_filenames[current_file_importing], "mov");
141 clip_complete(our_fetcher.clipfile);
143 total_time += our_fetcher.clipfile.length;
145 current_file_importing++;
147 if (current_file_importing <= filenames.size)
150 warning ("do_import_complete: current_file_importing out of bounds! %d %d".printf(
151 current_file_importing, filenames.size));
154 bool need_to_import(Fetcher f) {
155 //for now, use the clip as is
158 if (f.clipfile.is_of_type(MediaType.VIDEO)) {
160 if (f.clipfile.get_video_format(out format)) {
161 foreach (string s in no_import_formats) {
162 if (format == *(uint32*)s) {
173 void on_fetcher_ready(Fetcher f) {
174 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_fetcher_ready");
176 if (f.error_string != null) {
177 error_occurred(f.error_string);
178 do_import_complete();
182 if (need_to_import(f)) {
184 if (md5_checksum_on_file(f.clipfile.filename, out checksum)) {
185 string base_filename = import_directory + isolate_filename(f.clipfile.filename);
188 string new_filename = base_filename;
190 string existing_checksum;
191 if (get_file_md5_checksum(new_filename, out existing_checksum)) {
192 if (checksum == existing_checksum) {
193 // Re-fetch this clip to get the correct caps
194 filenames[current_file_importing] =
195 append_extension(new_filename, "mov");
196 current_file_importing--;
197 total_time -= f.clipfile.length;
201 new_filename = base_filename + index.to_string();
203 // Truly need to import
204 save_file_md5_checksum(new_filename, checksum);
205 queued_filenames.add(new_filename);
206 queued_fetchers.add(f as ClipFetcher);
211 error("Cannot get md5 checksum for file %s!", f.clipfile.filename);
213 clip_complete(f.clipfile);
215 do_import_complete();
217 error_occurred(e.message);
221 void do_import(ClipFetcher f) throws Error {
222 file_updated(f.clipfile.filename, current_file_importing);
227 pipeline = new Gst.Pipeline("pipeline");
228 pipeline.set_auto_flush_bus(false);
230 Gst.Bus bus = pipeline.get_bus();
231 bus.add_signal_watch();
233 bus.message["state-changed"] += on_state_changed;
234 bus.message["eos"] += on_eos;
235 bus.message["error"] += on_error;
236 bus.message["warning"] += on_warning;
238 mux = make_element("qtmux");
240 filesink = make_element("filesink");
241 filesink.set("location", append_extension(queued_filenames[current_file_importing], "mov"));
243 pipeline.add_many(mux, filesink);
245 if (f.clipfile.is_of_type(MediaType.VIDEO)) {
246 video_convert = make_element("ffmpegcolorspace");
247 pipeline.add(video_convert);
249 video_decoder = new SingleDecodeBin(Gst.Caps.from_string(
252 f.clipfile.filename);
253 video_decoder.pad_added.connect(on_pad_added);
255 pipeline.add(video_decoder);
257 if (!video_convert.link(mux))
258 error("do_import: Cannot link video converter to mux!");
260 if (f.clipfile.is_of_type(MediaType.AUDIO)) {
261 audio_convert = make_element("audioconvert");
262 pipeline.add(audio_convert);
264 // setup for importing h.264 and other int flavors. change to audio/x-raw-float
265 // if you need to import ogg and other float flavors. see bug 2055
266 audio_decoder = new SingleDecodeBin(
267 Gst.Caps.from_string("audio/x-raw-int"),
268 "audiodecodebin", f.clipfile.filename);
269 audio_decoder.pad_added.connect(on_pad_added);
271 pipeline.add(audio_decoder);
273 if (!audio_convert.link(mux))
274 error("do_import: Cannot link audio convert to mux!");
277 if (!mux.link(filesink))
278 error("do_import: Cannot link mux to filesink!");
280 emit(this, Facility.IMPORT, Level.VERBOSE,
281 "Starting import to %s...".printf(queued_filenames[current_file_importing]));
282 pipeline.set_state(Gst.State.PLAYING);
285 void on_pad_added(Gst.Pad p) {
286 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_pad_added");
288 string str = p.caps.to_string();
291 if (str.has_prefix("video")) {
293 sink = video_convert.get_compatible_pad(p, p.caps);
294 } else if (str.has_prefix("audio")) {
296 sink = audio_convert.get_compatible_pad(p, p.caps);
298 //error_occurred here gives a segfault
299 warning("Unrecognized prefix %s".printf(str));
303 if (p.link(sink) != Gst.PadLinkReturn.OK) {
304 error("Cannot link pad in importer!");
308 void on_error(Gst.Bus bus, Gst.Message message) {
309 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_error");
312 message.parse_error(out e, out text);
313 warning("%s\n", text);
314 error_occurred(text);
317 void on_warning(Gst.Bus bus, Gst.Message message) {
318 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_warning");
321 message.parse_warning(out e, out text);
325 void on_state_changed(Gst.Bus b, Gst.Message m) {
326 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_state_changed");
327 if (m.src != pipeline)
334 m.parse_state_changed (out old_state, out new_state, out pending);
336 if (old_state == new_state)
339 emit(this, Facility.IMPORT, Level.VERBOSE,
340 "Import State in %s".printf(new_state.to_string()));
341 if (new_state == Gst.State.PAUSED) {
343 if (video_pad != null) {
344 our_fetcher.clipfile.video_caps = video_pad.caps;
346 if (audio_pad != null) {
347 our_fetcher.clipfile.audio_caps = audio_pad.caps;
349 emit(this, Facility.IMPORT, Level.VERBOSE,
350 "Got clipfile info for: %s".printf(our_fetcher.clipfile.filename));
352 } else if (new_state == Gst.State.NULL) {
353 if (import_state == ImportState.CANCELLED) {
354 GLib.FileUtils.remove(append_extension(queued_filenames[current_file_importing],
356 GLib.FileUtils.remove(append_extension(queued_filenames[current_file_importing],
361 do_import_complete();
363 error_occurred(e.message);
370 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_eos");
372 pipeline.set_state(Gst.State.NULL);
376 public class LibraryImporter : Object {
377 protected Project project;
378 public ClipImporter importer;
380 public signal void started(ClipImporter i, int num);
382 public LibraryImporter(Project p) {
385 importer = new ClipImporter();
386 importer.clip_complete.connect(on_clip_complete);
387 importer.error_occurred.connect(on_error_occurred);
388 importer.importing_started.connect(on_importer_started);
391 void on_importer_started(ClipImporter i, int num) {
392 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_importer_started");
396 void on_error_occurred(string error) {
397 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_error_occurred");
398 emit(this, Facility.DEVELOPER_WARNINGS, Level.INFO, error);
399 project.error_occurred("Error importing", "An error occurred importing this file.");
402 protected virtual void append_existing_clipfile(ClipFile f) {
406 protected virtual void on_clip_complete(ClipFile f) {
407 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_complete");
408 ClipFile cf = project.find_clipfile(f.filename);
410 project.add_clipfile(f);
414 public void add_file(string filename) throws Error {
415 ClipFile cf = project.find_clipfile(filename);
418 append_existing_clipfile(cf);
420 importer.add_filename(filename);
423 public void start() throws Error {
428 public class TimelineImporter : LibraryImporter {
433 public TimelineImporter(Track track, Project p, int64 time_to_add, bool both_tracks) {
436 this.time_to_add = time_to_add;
437 this.both_tracks = both_tracks;
440 void add_to_both(ClipFile clip_file) {
443 if (track is Model.VideoTrack) {
444 other_track = project.find_audio_track();
446 other_track = project.find_video_track();
448 if (other_track != null) {
449 project.add(other_track, clip_file, time_to_add);
454 protected override void append_existing_clipfile(ClipFile f) {
455 project.undo_manager.start_transaction("Create Clip");
456 project.add(track, f, time_to_add);
458 project.undo_manager.end_transaction("Create Clip");
461 protected override void on_clip_complete(ClipFile f) {
462 project.undo_manager.start_transaction("Create Clip");
463 base.on_clip_complete(f);
464 project.add(track, f, time_to_add);
466 project.undo_manager.end_transaction("Create Clip");