00e7d2ec5c619c9042ccf9ed45efb905c64443b3
[ardour.git] / gtk2_ardour / transcode_video_dialog.cc
1 /*
2     Copyright (C) 2010 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 "video_copy_dialog.h"
49 #include "utils_videotl.h"
50 #include "i18n.h"
51
52 using namespace Gtk;
53 using namespace std;
54 using namespace PBD;
55 using namespace ARDOUR;
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 (_("Transcode Video\n And Import"))
63         , copy_button (_("Copy Video\nFile Only"))
64         , audio_button (_("Extract and\nImport Audio Only"))
65         , abort_button (_("Abort"))
66         , progress_label ()
67         , aspect_checkbox (_("Height = "))
68         , height_adjustment (128, 0, 1920, 1, 16, 0)
69         , height_spinner (height_adjustment)
70         , bitrate_checkbox (_("Manual Override"))
71         , bitrate_adjustment (2000, 100, 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         pending_copy_file = false;
83
84         set_name ("TranscodeVideoDialog");
85         set_position (Gtk::WIN_POS_MOUSE);
86         set_modal (true);
87         set_skip_taskbar_hint (true);
88         set_resizable (false);
89
90         Gtk::Label* l;
91         vbox = manage (new VBox);
92         VBox* options_box = manage (new VBox);
93         HBox* path_hbox = manage (new HBox);
94
95         int w = 0, h = 0;
96         m_aspect = 4.0/3.0;
97         AudioStreams as; as.clear();
98
99         path_hbox->pack_start (path_label, false, false, 3);
100         path_hbox->pack_start (path_entry, true, true, 3);
101         path_hbox->pack_start (browse_button, false, false, 3);
102         browse_button.set_name ("PaddedButton");
103
104         path_entry.set_width_chars(38);
105         height_spinner.set_sensitive(false);
106         bitrate_spinner.set_sensitive(false);
107
108         std::string dstdir = video_dest_dir(_session->session_directory().video_path(), Config->get_video_server_docroot());
109         std::string dstfn  = video_dest_file(dstdir, infile);
110         path_entry.set_text (dstfn);
111
112         l = manage (new Label (_("<b>Info</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
113         l->set_use_markup ();
114         options_box->pack_start (*l, false, true, 4);
115
116
117         if (!transcoder->ffexec_ok()) {
118                 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));
119                 l->set_line_wrap();
120                 options_box->pack_start (*l, false, true, 4);
121                 transcode_button.set_sensitive(false);
122                 aspect_checkbox.set_sensitive(false);
123                 bitrate_checkbox.set_sensitive(false);
124         }
125         else if (!transcoder->probe_ok()) {
126                 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));
127                 options_box->pack_start (*l, false, true, 4);
128                 transcode_button.set_sensitive(false);
129                 aspect_checkbox.set_sensitive(false);
130                 bitrate_checkbox.set_sensitive(false);
131         } else {
132                 w = transcoder->get_width();
133                 h = transcoder->get_height();
134                 as = transcoder->get_audio();
135                 m_aspect = transcoder->get_aspect();
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>Options</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
183         l->set_use_markup ();
184         options_box->pack_start (*l, false, true, 4);
185
186         Table* t = manage (new Table (4, 3));
187         t->set_spacings (4);
188         options_box->pack_start (*t, true, true, 4);
189
190         l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
191         t->attach (*l, 0, 1, 0, 1);
192         scale_combo.set_name ("PaddedButton");
193         t->attach (scale_combo, 1, 2, 0, 1);
194         t->attach (aspect_checkbox, 2, 3, 0, 1);
195         t->attach (height_spinner, 3, 4, 0, 1);
196
197         scale_combo.append_text(_("Original Width"));
198         if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
199         if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
200         if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
201         if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
202         if (w > 852)  { scale_combo.append_text(" 852 (hd480)"); }
203         if (w > 768)  { scale_combo.append_text(" 768 (PAL)"); }
204         if (w > 720)  { scale_combo.append_text(" 720 (PAL)"); }
205         if (w > 640)  { scale_combo.append_text(" 640 (vga, ega)"); }
206         if (w > 352)  { scale_combo.append_text(" 352 (cif)"); }
207         if (w > 320)  { scale_combo.append_text(" 320 (cga, qvga)"); }
208         if (w > 176)  { scale_combo.append_text(" 176 (qcif)"); }
209         scale_combo.set_active(0);
210         height_spinner.set_value(h);
211
212         l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
213         t->attach (*l, 0, 1, 1, 2);
214         t->attach (bitrate_checkbox, 2, 3, 1, 2);
215         t->attach (bitrate_spinner, 3, 4, 1, 2);
216
217         l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
218         t->attach (*l, 0, 1, 2, 3);
219         audio_combo.set_name ("PaddedButton");
220         t->attach (audio_combo, 1, 4, 2, 3);
221         audio_combo.append_text("No audio");
222         if (as.size() > 0) {
223                 for (AudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
224                         audio_combo.append_text((*it).name);
225                 }
226         }
227         audio_combo.set_active(0);
228
229 #if 1 /* tentative debug mode */
230         options_box->pack_start (debug_checkbox, false, true, 4);
231 #endif
232
233         vbox->pack_start (*path_hbox, false, false);
234         vbox->pack_start (*options_box, false, true);
235
236         get_vbox()->set_spacing (4);
237         get_vbox()->pack_start (*vbox, false, false);
238
239         progress_box = manage (new VBox);
240         progress_box->pack_start (progress_label, false, false);
241         progress_box->pack_start (pbar, false, false);
242         progress_box->pack_start (abort_button, false, false);
243         get_vbox()->pack_start (*progress_box, false, false);
244
245         browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
246         copy_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::prepare_copy));
247         audio_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_audioonly));
248         transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
249         abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
250
251         audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
252         scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
253         aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
254         height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
255         bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
256
257         update_bitrate();
258
259         audio_button.set_sensitive(false);
260
261         cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
262         get_action_area()->pack_start (audio_button, false, false);
263         get_action_area()->pack_start (copy_button, false, false);
264         get_action_area()->pack_start (transcode_button, false, false);
265         show_all_children ();
266         progress_box->hide();
267 }
268
269 TranscodeVideoDialog::~TranscodeVideoDialog ()
270 {
271         delete transcoder;
272 }
273
274 void
275 TranscodeVideoDialog::on_show ()
276 {
277         Dialog::on_show ();
278 }
279
280 void
281 TranscodeVideoDialog::abort_clicked ()
282 {
283         aborted = true;
284         transcoder->cancel();
285 }
286
287 void
288 TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
289 {
290         if (a == 0 || c > a) {
291                 pbar.set_pulse_step(.5);
292                 pbar.pulse();
293                 return;
294         }
295         pbar.set_fraction ((double)c / (double) a);
296 }
297
298 void
299 TranscodeVideoDialog::finished ()
300 {
301         if (aborted) {
302                 unlink(path_entry.get_text().c_str());
303                 if (!audiofile.empty()) {
304                         unlink(audiofile.c_str());
305                 }
306                 Gtk::Dialog::response(RESPONSE_CANCEL);
307         } else {
308                 if (pending_audio_extract || pending_copy_file) {
309                         StartNextStage();
310                 } else {
311                   Gtk::Dialog::response(RESPONSE_ACCEPT);
312                 }
313         }
314 }
315
316 void
317 TranscodeVideoDialog::prepare_copy ()
318 {
319         dialog_progress_mode();
320 #if 1 /* tentative debug mode */
321         if (debug_checkbox.get_active()) {
322                 transcoder->set_debug(true);
323         }
324 #endif
325
326         if (audio_combo.get_active_row_number() == 0) {
327                 launch_copy();
328         } else {
329                 aborted = false;
330                 pending_copy_file = true;
331                 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_copy , this), gui_context());
332                 transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
333                 transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
334                 launch_extract();
335         }
336 }
337
338 void
339 TranscodeVideoDialog::launch_audioonly ()
340 {
341         dialog_progress_mode();
342         path_entry.set_text("");
343 #if 1 /* tentative debug mode */
344         if (debug_checkbox.get_active()) {
345                 transcoder->set_debug(true);
346         }
347 #endif
348         if (audio_combo.get_active_row_number() == 0) {
349                 return;
350         }
351         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
352         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
353         launch_extract();
354 }
355
356 void
357 TranscodeVideoDialog::launch_copy ()
358 {
359         hide();
360         VideoCopyDialog *video_copy_dialog;
361         video_copy_dialog = new VideoCopyDialog(_session, infn);
362         video_copy_dialog->setup_non_interactive_copy(path_entry.get_text());
363         ResponseType r = (ResponseType) video_copy_dialog->run ();
364         video_copy_dialog->hide();
365         delete video_copy_dialog;
366         Gtk::Dialog::response(r);
367 }
368
369 void
370 TranscodeVideoDialog::launch_extract ()
371 {
372         audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp & check if file exists in audiofiles */
373         /* think: do_embed() vs do_import() - editor_videotimeline.cc
374          * directly use _session->session_directory().sound_path() ?!
375          */
376         int audio_stream;
377         pending_audio_extract = false;
378         aborted = false;
379         audio_stream = audio_combo.get_active_row_number() -1;
380         progress_label.set_text (_("Extracting Audio.."));
381
382         if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
383                 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
384                 audiofile="";
385                 Gtk::Dialog::response(RESPONSE_CANCEL);
386                 return;
387         }
388 }
389
390 void
391 TranscodeVideoDialog::dialog_progress_mode ()
392 {
393         vbox->hide();
394         cancel_button->hide();
395         copy_button.hide();
396         transcode_button.hide();
397         pbar.set_size_request(300,-1);
398         progress_box->show();
399 }
400
401 void
402 TranscodeVideoDialog::launch_transcode ()
403 {
404         std::string outfn = path_entry.get_text();
405         if (!confirm_video_outfn(outfn, Config->get_video_server_docroot())) return;
406         progress_label.set_text (_("Transcoding Video.."));
407         dialog_progress_mode();
408 #if 1 /* tentative debug mode */
409         if (debug_checkbox.get_active()) {
410                 transcoder->set_debug(true);
411         }
412 #endif
413
414         aborted = false;
415         if (audio_combo.get_active_row_number() != 0) {
416                 pending_audio_extract = true;
417                 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
418         }
419
420         int scale_width, scale_height, bitrate;
421         if (scale_combo.get_active_row_number() == 0 ) {
422                 scale_width =0;
423         } else {
424           scale_width = atoi(scale_combo.get_active_text().c_str());
425         }
426         if (!aspect_checkbox.get_active()) {
427                 scale_height = 0;
428         } else {
429                 scale_height = (int) floor(height_spinner.get_value());
430         }
431         if (bitrate_checkbox.get_active() ){
432                 bitrate = (int) floor(bitrate_spinner.get_value());
433         } else {
434                 bitrate = 0;
435         }
436
437         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
438         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
439         if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
440                 ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
441                 Gtk::Dialog::response(RESPONSE_CANCEL);
442                 return;
443         }
444 }
445
446 void
447 TranscodeVideoDialog::audio_combo_changed ()
448 {
449         bool use_audio = audio_combo.get_active_row_number() != 0;
450         audio_button.set_sensitive(use_audio);
451         if (use_audio) {
452                 copy_button.set_label(_("Copy File And\nExtract Audio"));
453         } else {
454                 copy_button.set_label(_("Copy Video\nFile Only"));
455         }
456 }
457
458 void
459 TranscodeVideoDialog::scale_combo_changed ()
460 {
461         update_bitrate();
462         if (!aspect_checkbox.get_active()) {
463                 int h;
464                 if (scale_combo.get_active_row_number() == 0 ) {
465                         h = transcoder->get_height();
466                 } else {
467                         h = floor(atof(scale_combo.get_active_text().c_str()) / m_aspect);
468                 }
469                 height_spinner.set_value(h);
470         }
471 }
472
473 void
474 TranscodeVideoDialog::aspect_checkbox_toggled ()
475 {
476         height_spinner.set_sensitive(aspect_checkbox.get_active());
477         scale_combo_changed();
478 }
479
480 void
481 TranscodeVideoDialog::bitrate_checkbox_toggled ()
482 {
483         bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
484         if (!bitrate_checkbox.get_active()) {
485                 update_bitrate();
486         }
487 }
488
489 void
490 TranscodeVideoDialog::update_bitrate ()
491 {
492         double br = .7; /* avg quality - bits per pixel */
493         if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
494         br *= transcoder->get_fps();
495         br *= height_spinner.get_value();
496         if (scale_combo.get_active_row_number() == 0 ) {
497                 br *= transcoder->get_height();
498         } else {
499                 br *= atof(scale_combo.get_active_text().c_str());
500         }
501         bitrate_spinner.set_value(floor(br/10000.0)*10);
502 }
503
504 void
505 TranscodeVideoDialog::open_browse_dialog ()
506 {
507         Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
508         dialog.set_filename (path_entry.get_text());
509
510         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
511         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
512
513         int result = dialog.run();
514
515         if (result == Gtk::RESPONSE_OK) {
516                 std::string filename = dialog.get_filename();
517
518                 if (filename.length()) {
519                         path_entry.set_text (filename);
520                 }
521         }
522 }
523
524 #endif /* WITH_VIDEOTIMELINE */