Optimize automation-event process splitting
[ardour.git] / gtk2_ardour / transcode_video_dialog.cc
1 /*
2     Copyright (C) 2010,2013 Paul Davis
3     Author: Robin Gareus <robin@gareus.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20 #include <cstdio>
21 #include <string>
22 #include <sstream>
23 #include <iomanip>
24
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include <sigc++/bind.h>
31
32 #include <gtkmm/filechooserdialog.h>
33 #include <gtkmm/stock.h>
34
35 #include "pbd/gstdio_compat.h"
36
37 #include "pbd/error.h"
38 #include "pbd/convert.h"
39 #include "gtkmm2ext/utils.h"
40 #include "ardour/session_directory.h"
41 #include "ardour/profile.h"
42 #include "ardour/template_utils.h"
43 #include "ardour/session.h"
44 #include "ardour_ui.h"
45 #include "gui_thread.h"
46
47 #include "opts.h"
48 #include "transcode_video_dialog.h"
49 #include "utils_videotl.h"
50 #include "pbd/i18n.h"
51
52 using namespace Gtk;
53 using namespace std;
54 using namespace PBD;
55 using namespace ARDOUR;
56 using namespace VideoUtils;
57
58 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
59         : ArdourDialog (_("Transcode/Import Video File "))
60         , infn (infile)
61         , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
62         , browse_button (_("Browse"))
63         , transcode_button (_("OK"))
64         , abort_button (_("Abort"))
65         , progress_label ()
66         , aspect_checkbox (_("Height = "))
67         , height_adjustment (128, 0, 1920, 1, 16, 0)
68         , height_spinner (height_adjustment)
69         , ltc_detect (_("Extract LTC from audio and align video"))
70         , bitrate_checkbox (_("Manual Override"))
71         , bitrate_adjustment (2000, 500, 10000, 10, 100, 0)
72         , bitrate_spinner (bitrate_adjustment)
73 #if 1 /* tentative debug mode */
74         , debug_checkbox (_("Debug Mode: Print ffmpeg command and output to stdout."))
75 #endif
76 {
77         set_session (s);
78
79         transcoder = new TranscodeFfmpeg(infile);
80         audiofile = "";
81         pending_audio_extract = false;
82         aborted = false;
83
84         set_name ("TranscodeVideoDialog");
85         set_modal (true);
86         set_skip_taskbar_hint (true);
87         set_resizable (false);
88
89         Gtk::Label* l;
90         vbox = manage (new VBox);
91         VBox* options_box = manage (new VBox);
92         HBox* path_hbox = manage (new HBox);
93
94         int w = 0, h = 0;
95         m_aspect = 4.0/3.0;
96         TranscodeFfmpeg::FFAudioStreams as; as.clear();
97
98         path_hbox->pack_start (path_label, false, false, 3);
99         path_hbox->pack_start (path_entry, true, true, 3);
100         path_hbox->pack_start (browse_button, false, false, 3);
101
102         path_entry.set_width_chars(38);
103         height_spinner.set_sensitive(false);
104         bitrate_spinner.set_sensitive(false);
105
106         std::string dstdir = video_dest_dir(_session->session_directory().video_path(), video_get_docroot(Config));
107         std::string dstfn  = video_dest_file(dstdir, infile);
108         path_entry.set_text (dstfn);
109
110         l = manage (new Label (_("<b>File Information</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
111         l->set_use_markup ();
112         options_box->pack_start (*l, false, true, 4);
113
114         bool ffok = false;
115         if (!transcoder->ffexec_ok()) {
116                 l = manage (new Label (_("ffmpeg installation was not found. Video Import is not possible. See the Log window for more information."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
117                 l->set_line_wrap();
118                 options_box->pack_start (*l, false, true, 4);
119                 aspect_checkbox.set_sensitive(false);
120                 bitrate_checkbox.set_sensitive(false);
121         }
122         else if (!transcoder->probe_ok()) {
123                 l = manage (new Label (string_compose(_("File-info can not be read. Most likely '%1' is not a valid video-file or an unsupported video codec or format."), infn), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
124                 options_box->pack_start (*l, false, true, 4);
125                 aspect_checkbox.set_sensitive(false);
126                 bitrate_checkbox.set_sensitive(false);
127         } else {
128                 w = transcoder->get_width();
129                 h = transcoder->get_height();
130                 as = transcoder->get_audio();
131                 m_aspect = transcoder->get_aspect();
132
133                 if (w > 0 && h > 0 && transcoder->get_fps() > 0 && transcoder->get_duration() > 0) {
134                         ffok = true;
135                 }
136
137                 Table* t = manage (new Table (4, 2));
138                 t->set_spacings (4);
139                 options_box->pack_start (*t, true, true, 4);
140                 l = manage (new Label (_("FPS:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
141                 t->attach (*l, 0, 1, 0, 1);
142                 l = manage (new Label (_("Duration:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
143                 t->attach (*l, 2, 3, 0, 1);
144                 l = manage (new Label (_("Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
145                 t->attach (*l, 0, 1, 1, 2);
146                 l = manage (new Label (_("Geometry:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
147                 t->attach (*l, 2, 3, 1, 2);
148
149                 std::ostringstream osstream;
150                 osstream << transcoder->get_fps();
151                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
152                 t->attach (*l, 1, 2, 0, 1);
153
154                 osstream.str("");
155                 osstream << w << "x" << h;
156                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
157                 t->attach (*l, 3, 4, 1, 2);
158
159                 osstream.str("");
160                 if (transcoder->get_duration() == 0 || transcoder->get_fps() == 0) {
161                         osstream << _("??");
162                 } else {
163                         unsigned long sec = transcoder->get_duration() / transcoder->get_fps();
164                         osstream << setfill('0') << setw(2);
165                         osstream << (sec / 3600) << ":";
166                         osstream << setfill('0') << setw(2);
167                         osstream << ((sec /60 )%60) << ":";
168                         osstream << setfill('0') << setw(2);
169                         osstream << (sec%60)  << ":";
170                         osstream << setfill('0') << setw(2);
171                         osstream << (transcoder->get_duration() % (int) floor(transcoder->get_fps()));
172                 }
173                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
174                 t->attach (*l, 3, 4, 0, 1);
175
176                 osstream.str("");
177                 osstream << transcoder->get_codec();
178                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
179                 t->attach (*l, 1, 2, 1, 2);
180         }
181
182         l = manage (new Label (_("<b>Import Settings</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
183         l->set_use_markup ();
184         options_box->pack_start (*l, false, true, 4);
185
186         if (ffok) {
187                 video_combo.append_text(_("Reference from Current Location (Previously Transcoded Files Only)"));
188                 video_combo.append_text(_("Import/Transcode Video to Session"));
189                 video_combo.set_active(1);
190                 if (as.size() > 0) {
191                         video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
192                         audio_combo.set_sensitive(true);
193                 } else {
194                         audio_combo.set_sensitive(false);
195                 }
196                 video_combo.set_sensitive(true);
197                 transcode_button.set_sensitive(true);
198                 path_entry.set_sensitive (true);
199                 browse_button.set_sensitive (true);
200         } else if (as.size() > 0) {
201                 video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
202                 video_combo.set_active(0);
203                 path_entry.set_text ("");
204
205                 video_combo.set_sensitive(false);
206                 audio_combo.set_sensitive(true);
207                 transcode_button.set_sensitive(true);
208                 path_entry.set_sensitive (false);
209                 browse_button.set_sensitive (false);
210         } else {
211                 video_combo.append_text(_("Do Not Import Video"));
212                 video_combo.set_active(0);
213                 path_entry.set_text ("");
214                 video_combo.set_sensitive(false);
215                 audio_combo.set_sensitive(false);
216                 transcode_button.set_sensitive(false);
217                 path_entry.set_sensitive (false);
218                 browse_button.set_sensitive (false);
219         }
220
221         options_box->pack_start (video_combo, false, false, 4);
222
223         Table* t = manage (new Table (4, 4));
224         t->set_spacings (4);
225         options_box->pack_start (*t, true, true, 4);
226
227         l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
228         t->attach (*l, 0, 1, 0, 1);
229         t->attach (scale_combo, 1, 2, 0, 1);
230         t->attach (aspect_checkbox, 2, 3, 0, 1);
231         t->attach (height_spinner, 3, 4, 0, 1);
232
233         scale_combo.append_text(_("Original Width"));
234         if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
235         if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
236         if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
237         if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
238         if (w > 852)  { scale_combo.append_text(" 852 (hd480)"); }
239         if (w > 768)  { scale_combo.append_text(" 768 (PAL)"); }
240         if (w > 720)  { scale_combo.append_text(" 720 (PAL)"); }
241         if (w > 640)  { scale_combo.append_text(" 640 (vga, ega)"); }
242         if (w > 352)  { scale_combo.append_text(" 352 (cif)"); }
243         if (w > 320)  { scale_combo.append_text(" 320 (cga, qvga)"); }
244         if (w > 176)  { scale_combo.append_text(" 176 (qcif)"); }
245         scale_combo.set_active(0);
246         height_spinner.set_value(h);
247
248         l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
249         t->attach (*l, 0, 1, 1, 2);
250         t->attach (bitrate_checkbox, 2, 3, 1, 2);
251         t->attach (bitrate_spinner, 3, 4, 1, 2);
252
253         l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
254         t->attach (*l, 0, 1, 2, 3);
255         t->attach (audio_combo, 1, 4, 2, 3);
256         t->attach (ltc_detect, 1, 4, 3, 4);
257         if (as.size() == 0) {
258                 audio_combo.append_text(_("No Audio Track Present"));
259                 audio_combo.set_sensitive(false);
260         } else {
261                 audio_combo.append_text(_("Do Not Extract Audio"));
262                 for (TranscodeFfmpeg::FFAudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
263                         audio_combo.append_text((*it).name);
264                 }
265         }
266         audio_combo.set_active(0);
267         ltc_detect.set_sensitive (false);
268
269 #if 1 /* tentative debug mode */
270         options_box->pack_start (debug_checkbox, false, true, 4);
271 #endif
272
273         vbox->pack_start (*path_hbox, false, false);
274         vbox->pack_start (*options_box, false, true);
275
276         get_vbox()->set_spacing (4);
277         get_vbox()->pack_start (*vbox, false, false);
278
279         progress_box = manage (new VBox);
280         progress_box->set_spacing(6);
281         progress_box->pack_start (progress_label, false, false);
282         progress_box->pack_start (pbar, false, false);
283         progress_box->pack_start (abort_button, false, false);
284         get_vbox()->pack_start (*progress_box, false, false);
285
286         browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
287         transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
288         abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
289
290         video_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed));
291         audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
292         scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
293         aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
294         height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
295         bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
296
297         update_bitrate();
298
299         cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
300         get_action_area()->pack_start (transcode_button, false, false);
301         show_all_children ();
302         progress_box->hide();
303 }
304
305 TranscodeVideoDialog::~TranscodeVideoDialog ()
306 {
307         delete transcoder;
308 }
309
310 void
311 TranscodeVideoDialog::on_show ()
312 {
313         Dialog::on_show ();
314 }
315
316 void
317 TranscodeVideoDialog::abort_clicked ()
318 {
319         aborted = true;
320         transcoder->cancel();
321 }
322
323 void
324 TranscodeVideoDialog::update_progress (samplecnt_t c, samplecnt_t a)
325 {
326         if (a == 0 || c > a) {
327                 pbar.set_pulse_step(.5);
328                 pbar.pulse();
329                 return;
330         }
331         pbar.set_fraction ((double)c / (double) a);
332 }
333
334 void
335 TranscodeVideoDialog::finished ()
336 {
337         if (aborted) {
338                 ::g_unlink(path_entry.get_text().c_str());
339                 if (!audiofile.empty()) {
340                         ::g_unlink(audiofile.c_str());
341                 }
342                 Gtk::Dialog::response(RESPONSE_CANCEL);
343         } else {
344                 if (pending_audio_extract) {
345                         StartNextStage();
346                 } else {
347                   Gtk::Dialog::response(RESPONSE_ACCEPT);
348                 }
349         }
350 }
351
352 void
353 TranscodeVideoDialog::launch_audioonly ()
354 {
355         if (audio_combo.get_active_row_number() == 0) {
356                 finished();
357                 return;
358         }
359         dialog_progress_mode();
360 #if 1 /* tentative debug mode */
361         if (debug_checkbox.get_active()) {
362                 transcoder->set_debug(true);
363         }
364 #endif
365         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
366         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
367         launch_extract();
368 }
369
370 void
371 TranscodeVideoDialog::launch_extract ()
372 {
373         audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp */
374         int audio_stream;
375         pending_audio_extract = false;
376         aborted = false;
377         audio_stream = audio_combo.get_active_row_number() -1;
378         progress_label.set_text (_("Extracting Audio.."));
379
380         if (!transcoder->extract_audio(audiofile, _session->nominal_sample_rate(), audio_stream)) {
381                 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
382                 audiofile="";
383                 Gtk::Dialog::response(RESPONSE_CANCEL);
384                 return;
385         }
386 }
387
388 void
389 TranscodeVideoDialog::dialog_progress_mode ()
390 {
391         vbox->hide();
392         cancel_button->hide();
393         transcode_button.hide();
394         pbar.set_size_request(300,-1);
395         progress_box->show();
396 }
397
398 void
399 TranscodeVideoDialog::launch_transcode ()
400 {
401         if (video_combo.get_active_row_number() != 1) {
402                 launch_audioonly();
403                 return;
404         }
405         std::string outfn = path_entry.get_text();
406         if (!confirm_video_outfn(*this, outfn, video_get_docroot(Config))) return;
407         progress_label.set_text (_("Transcoding Video.."));
408         dialog_progress_mode();
409 #if 1 /* tentative debug mode */
410         if (debug_checkbox.get_active()) {
411                 transcoder->set_debug(true);
412         }
413 #endif
414
415         aborted = false;
416         if (audio_combo.get_active_row_number() != 0) {
417                 pending_audio_extract = true;
418                 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
419         }
420
421         int scale_width, scale_height, bitrate;
422         if (scale_combo.get_active_row_number() == 0 ) {
423                 scale_width =0;
424         } else {
425           scale_width = atoi(scale_combo.get_active_text());
426         }
427         if (!aspect_checkbox.get_active()) {
428                 scale_height = 0;
429         } else {
430                 scale_height = (int) floor(height_spinner.get_value());
431         }
432         if (bitrate_checkbox.get_active() ){
433                 bitrate = (int) floor(bitrate_spinner.get_value());
434         } else {
435                 bitrate = 0;
436         }
437
438         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
439         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
440         if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
441                 ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
442                 Gtk::Dialog::response(RESPONSE_CANCEL);
443                 return;
444         }
445 }
446
447 void
448 TranscodeVideoDialog::video_combo_changed ()
449 {
450         const int i = video_combo.get_active_row_number();
451         if (i != 1) {
452                 scale_combo.set_sensitive(false);
453                 aspect_checkbox.set_sensitive(false);
454                 height_spinner.set_sensitive(false);
455                 bitrate_checkbox.set_sensitive(false);
456                 bitrate_spinner.set_sensitive(false);
457         } else {
458                 scale_combo.set_sensitive(true);
459                 aspect_checkbox.set_sensitive(true);
460                 height_spinner.set_sensitive(true);
461                 bitrate_checkbox.set_sensitive(true);
462                 bitrate_spinner.set_sensitive(true);
463         }
464         if (i == 2 && audio_combo.get_active_row_number() == 0) {
465                 audio_combo.set_active(1);
466         } else {
467                 //update LTC option sensitivity
468                 audio_combo_changed ();
469         }
470 }
471
472 void
473 TranscodeVideoDialog::audio_combo_changed ()
474 {
475         if (video_combo.get_active_row_number() == 2
476                         && audio_combo.get_active_row_number() == 0)
477         {
478                 audio_combo.set_active(1);
479                 ltc_detect.set_sensitive (false);
480                 ltc_detect.set_active (false);
481         }
482
483         if (video_combo.get_active_row_number() != 2
484                         && audio_combo.get_active_row_number() > 0)
485         {
486                 ltc_detect.set_sensitive (true);
487         } else {
488                 ltc_detect.set_sensitive (false);
489                 ltc_detect.set_active (false);
490         }
491 }
492
493 void
494 TranscodeVideoDialog::scale_combo_changed ()
495 {
496         if (!aspect_checkbox.get_active()) {
497                 int h;
498                 if (scale_combo.get_active_row_number() == 0 ) {
499                         h = transcoder->get_height();
500                 } else {
501                         h = floor(atof(scale_combo.get_active_text()) / m_aspect);
502                 }
503                 height_spinner.set_value(h);
504         }
505         update_bitrate();
506 }
507
508 void
509 TranscodeVideoDialog::aspect_checkbox_toggled ()
510 {
511         height_spinner.set_sensitive(aspect_checkbox.get_active());
512         scale_combo_changed();
513 }
514
515 void
516 TranscodeVideoDialog::bitrate_checkbox_toggled ()
517 {
518         bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
519         if (!bitrate_checkbox.get_active()) {
520                 update_bitrate();
521         }
522 }
523
524 void
525 TranscodeVideoDialog::update_bitrate ()
526 {
527         double br = .7; /* avg quality - bits per pixel */
528         if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
529         br *= transcoder->get_fps();
530         br *= height_spinner.get_value();
531
532         if (scale_combo.get_active_row_number() == 0 ) {
533                 br *= transcoder->get_width();
534         } else {
535                 br *= atof(scale_combo.get_active_text());
536         }
537         if (br != 0) {
538                 bitrate_spinner.set_value(floor(br/10000.0)*10);
539         }
540 }
541
542 void
543 TranscodeVideoDialog::open_browse_dialog ()
544 {
545         Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
546         dialog.set_filename (path_entry.get_text());
547
548         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
549         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
550
551         int result = dialog.run();
552
553         if (result == Gtk::RESPONSE_OK) {
554                 std::string filename = dialog.get_filename();
555
556                 if (filename.length()) {
557                         path_entry.set_text (filename);
558                 }
559         }
560 }
561
562 enum VtlTranscodeOption
563 TranscodeVideoDialog::import_option() {
564         int i = video_combo.get_active_row_number();
565         return static_cast<VtlTranscodeOption>(i);
566 }