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