Merge branch 'patches' of https://github.com/jdekozak/ardour
[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 #ifdef WITH_VIDEOTIMELINE
21
22 #include <cstdio>
23 #include <string>
24 #include <sstream>
25 #include <iomanip>
26
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31
32 #include <sigc++/bind.h>
33 #include <libgen.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
56 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
57         : ArdourDialog (_("Transcode/Import Video File "))
58         , infn (infile)
59         , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
60         , browse_button (_("Browse"))
61         , transcode_button (_("OK"))
62         , abort_button (_("Abort"))
63         , progress_label ()
64         , aspect_checkbox (_("Height = "))
65         , height_adjustment (128, 0, 1920, 1, 16, 0)
66         , height_spinner (height_adjustment)
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_position (Gtk::WIN_POS_MOUSE);
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         AudioStreams 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>Info</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
110         l->set_use_markup ();
111         options_box->pack_start (*l, false, true, 4);
112
113
114         bool ffok = false;
115         if (!transcoder->ffexec_ok()) {
116                 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 widow 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                 ffok = true;
129                 w = transcoder->get_width();
130                 h = transcoder->get_height();
131                 as = transcoder->get_audio();
132                 m_aspect = transcoder->get_aspect();
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>Video</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
180         l->set_use_markup ();
181         options_box->pack_start (*l, false, true, 4);
182
183         video_combo.set_name ("PaddedButton");
184         video_combo.append_text(_("Do Not Import Video"));
185         video_combo.append_text(_("Reference From Current Location"));
186         if (ffok)  {
187                 video_combo.append_text(_("Import/Transcode Video to Session"));
188                 video_combo.set_active(2);
189         } else {
190                 video_combo.set_active(1);
191                 video_combo.set_sensitive(false);
192                 audio_combo.set_sensitive(false);
193         }
194
195         options_box->pack_start (video_combo, false, false, 4);
196
197         Table* t = manage (new Table (4, 3));
198         t->set_spacings (4);
199         options_box->pack_start (*t, true, true, 4);
200
201         l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
202         t->attach (*l, 0, 1, 0, 1);
203         scale_combo.set_name ("PaddedButton");
204         t->attach (scale_combo, 1, 2, 0, 1);
205         t->attach (aspect_checkbox, 2, 3, 0, 1);
206         t->attach (height_spinner, 3, 4, 0, 1);
207
208         scale_combo.append_text(_("Original Width"));
209         if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
210         if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
211         if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
212         if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
213         if (w > 852)  { scale_combo.append_text(" 852 (hd480)"); }
214         if (w > 768)  { scale_combo.append_text(" 768 (PAL)"); }
215         if (w > 720)  { scale_combo.append_text(" 720 (PAL)"); }
216         if (w > 640)  { scale_combo.append_text(" 640 (vga, ega)"); }
217         if (w > 352)  { scale_combo.append_text(" 352 (cif)"); }
218         if (w > 320)  { scale_combo.append_text(" 320 (cga, qvga)"); }
219         if (w > 176)  { scale_combo.append_text(" 176 (qcif)"); }
220         scale_combo.set_active(0);
221         height_spinner.set_value(h);
222
223         l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
224         t->attach (*l, 0, 1, 1, 2);
225         t->attach (bitrate_checkbox, 2, 3, 1, 2);
226         t->attach (bitrate_spinner, 3, 4, 1, 2);
227
228         l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
229         t->attach (*l, 0, 1, 2, 3);
230         audio_combo.set_name ("PaddedButton");
231         t->attach (audio_combo, 1, 4, 2, 3);
232         audio_combo.append_text("No audio");
233         if (as.size() > 0) {
234                 for (AudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
235                         audio_combo.append_text((*it).name);
236                 }
237         }
238         audio_combo.set_active(0);
239
240 #if 1 /* tentative debug mode */
241         options_box->pack_start (debug_checkbox, false, true, 4);
242 #endif
243
244         vbox->pack_start (*path_hbox, false, false);
245         vbox->pack_start (*options_box, false, true);
246
247         get_vbox()->set_spacing (4);
248         get_vbox()->pack_start (*vbox, false, false);
249
250         progress_box = manage (new VBox);
251         progress_box->pack_start (progress_label, false, false);
252         progress_box->pack_start (pbar, false, false);
253         progress_box->pack_start (abort_button, false, false);
254         get_vbox()->pack_start (*progress_box, false, false);
255
256         browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
257         transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
258         abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
259
260         video_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed));
261         audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
262         scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
263         aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
264         height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
265         bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
266
267         update_bitrate();
268
269         cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
270         get_action_area()->pack_start (transcode_button, false, false);
271         show_all_children ();
272         progress_box->hide();
273 }
274
275 TranscodeVideoDialog::~TranscodeVideoDialog ()
276 {
277         delete transcoder;
278 }
279
280 void
281 TranscodeVideoDialog::on_show ()
282 {
283         Dialog::on_show ();
284 }
285
286 void
287 TranscodeVideoDialog::abort_clicked ()
288 {
289         aborted = true;
290         transcoder->cancel();
291 }
292
293 void
294 TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
295 {
296         if (a == 0 || c > a) {
297                 pbar.set_pulse_step(.5);
298                 pbar.pulse();
299                 return;
300         }
301         pbar.set_fraction ((double)c / (double) a);
302 }
303
304 void
305 TranscodeVideoDialog::finished ()
306 {
307         if (aborted) {
308                 unlink(path_entry.get_text().c_str());
309                 if (!audiofile.empty()) {
310                         unlink(audiofile.c_str());
311                 }
312                 Gtk::Dialog::response(RESPONSE_CANCEL);
313         } else {
314                 if (pending_audio_extract) {
315                         StartNextStage();
316                 } else {
317                   Gtk::Dialog::response(RESPONSE_ACCEPT);
318                 }
319         }
320 }
321
322 void
323 TranscodeVideoDialog::launch_audioonly ()
324 {
325         if (audio_combo.get_active_row_number() == 0) {
326                 finished();
327                 return;
328         }
329         dialog_progress_mode();
330 #if 1 /* tentative debug mode */
331         if (debug_checkbox.get_active()) {
332                 transcoder->set_debug(true);
333         }
334 #endif
335         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
336         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
337         launch_extract();
338 }
339
340 void
341 TranscodeVideoDialog::launch_extract ()
342 {
343         audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp */
344         int audio_stream;
345         pending_audio_extract = false;
346         aborted = false;
347         audio_stream = audio_combo.get_active_row_number() -1;
348         progress_label.set_text (_("Extracting Audio.."));
349
350         if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
351                 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
352                 audiofile="";
353                 Gtk::Dialog::response(RESPONSE_CANCEL);
354                 return;
355         }
356 }
357
358 void
359 TranscodeVideoDialog::dialog_progress_mode ()
360 {
361         vbox->hide();
362         cancel_button->hide();
363         transcode_button.hide();
364         pbar.set_size_request(300,-1);
365         progress_box->show();
366 }
367
368 void
369 TranscodeVideoDialog::launch_transcode ()
370 {
371         if (video_combo.get_active_row_number() != 2) {
372                 launch_audioonly();
373                 return;
374         }
375         std::string outfn = path_entry.get_text();
376         if (!confirm_video_outfn(outfn, video_get_docroot(Config))) return;
377         progress_label.set_text (_("Transcoding Video.."));
378         dialog_progress_mode();
379 #if 1 /* tentative debug mode */
380         if (debug_checkbox.get_active()) {
381                 transcoder->set_debug(true);
382         }
383 #endif
384
385         aborted = false;
386         if (audio_combo.get_active_row_number() != 0) {
387                 pending_audio_extract = true;
388                 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
389         }
390
391         int scale_width, scale_height, bitrate;
392         if (scale_combo.get_active_row_number() == 0 ) {
393                 scale_width =0;
394         } else {
395           scale_width = atoi(scale_combo.get_active_text().c_str());
396         }
397         if (!aspect_checkbox.get_active()) {
398                 scale_height = 0;
399         } else {
400                 scale_height = (int) floor(height_spinner.get_value());
401         }
402         if (bitrate_checkbox.get_active() ){
403                 bitrate = (int) floor(bitrate_spinner.get_value());
404         } else {
405                 bitrate = 0;
406         }
407
408         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
409         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
410         if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
411                 ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
412                 Gtk::Dialog::response(RESPONSE_CANCEL);
413                 return;
414         }
415 }
416
417 void
418 TranscodeVideoDialog::video_combo_changed ()
419 {
420         int i = video_combo.get_active_row_number();
421         if (i != 2) {
422                 scale_combo.set_sensitive(false);
423                 aspect_checkbox.set_sensitive(false);
424                 height_spinner.set_sensitive(false);
425                 bitrate_checkbox.set_sensitive(false);
426                 bitrate_spinner.set_sensitive(false);
427         } else {
428                 scale_combo.set_sensitive(true);
429                 aspect_checkbox.set_sensitive(true);
430                 height_spinner.set_sensitive(true);
431                 bitrate_checkbox.set_sensitive(true);
432                 bitrate_spinner.set_sensitive(true);
433         }
434 }
435
436 void
437 TranscodeVideoDialog::audio_combo_changed ()
438 {
439         ;
440 }
441
442 void
443 TranscodeVideoDialog::scale_combo_changed ()
444 {
445         if (!aspect_checkbox.get_active()) {
446                 int h;
447                 if (scale_combo.get_active_row_number() == 0 ) {
448                         h = transcoder->get_height();
449                 } else {
450                         h = floor(atof(scale_combo.get_active_text().c_str()) / m_aspect);
451                 }
452                 height_spinner.set_value(h);
453         }
454         update_bitrate();
455 }
456
457 void
458 TranscodeVideoDialog::aspect_checkbox_toggled ()
459 {
460         height_spinner.set_sensitive(aspect_checkbox.get_active());
461         scale_combo_changed();
462 }
463
464 void
465 TranscodeVideoDialog::bitrate_checkbox_toggled ()
466 {
467         bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
468         if (!bitrate_checkbox.get_active()) {
469                 update_bitrate();
470         }
471 }
472
473 void
474 TranscodeVideoDialog::update_bitrate ()
475 {
476         double br = .7; /* avg quality - bits per pixel */
477         if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
478         br *= transcoder->get_fps();
479         br *= height_spinner.get_value();
480
481         if (scale_combo.get_active_row_number() == 0 ) {
482                 br *= transcoder->get_width();
483         } else {
484                 br *= atof(scale_combo.get_active_text().c_str());
485         }
486         if (br != 0) {
487                 bitrate_spinner.set_value(floor(br/10000.0)*10);
488         }
489 }
490
491 void
492 TranscodeVideoDialog::open_browse_dialog ()
493 {
494         Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
495         dialog.set_filename (path_entry.get_text());
496
497         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
498         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
499
500         int result = dialog.run();
501
502         if (result == Gtk::RESPONSE_OK) {
503                 std::string filename = dialog.get_filename();
504
505                 if (filename.length()) {
506                         path_entry.set_text (filename);
507                 }
508         }
509 }
510
511 enum VtlTranscodeOption
512 TranscodeVideoDialog::import_option() {
513         int i = video_combo.get_active_row_number();
514         return static_cast<VtlTranscodeOption>(i);
515 }
516
517 #endif /* WITH_VIDEOTIMELINE */