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