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