ardour
transcode_video_dialog.cc
Go to the documentation of this file.
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"
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 
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 
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();
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 
278 {
279  delete transcoder;
280 }
281 
282 void
284 {
285  Dialog::on_show ();
286 }
287 
288 void
290 {
291  aborted = true;
292  transcoder->cancel();
293 }
294 
295 void
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
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
326 {
327  if (audio_combo.get_active_row_number() == 0) {
328  finished();
329  return;
330  }
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
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
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
372 {
373  if (video_combo.get_active_row_number() != 1) {
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.."));
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
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
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
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
468 {
469  height_spinner.set_sensitive(aspect_checkbox.get_active());
471 }
472 
473 void
475 {
476  bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
477  if (!bitrate_checkbox.get_active()) {
478  update_bitrate();
479  }
480 }
481 
482 void
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
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 
522  int i = video_combo.get_active_row_number();
523  return static_cast<VtlTranscodeOption>(i);
524 }
std::string video_get_docroot(ARDOUR::RCConfiguration *config)
framecnt_t nominal_frame_rate() const
Definition: session.h:367
Gtk::SpinButton bitrate_spinner
int atoi(const string &s)
Definition: convert.cc:140
std::string video_dest_file(const std::string, const std::string)
Gtk::SpinButton height_spinner
wrapper around ffmpeg and ffprobe command-line utils
const std::string video_path() const
TranscodeFfmpeg * transcoder
Definition: ardour_ui.h:130
static ARDOUR_UI * instance()
Definition: ardour_ui.h:187
Definition: Beats.hpp:239
Gtk::ComboBoxText audio_combo
#define invalidator(x)
Definition: gui_thread.h:40
#define _(Text)
Definition: i18n.h:11
PBD::Signal2< void, ARDOUR::framecnt_t, ARDOUR::framecnt_t > Progress
Gtk::CheckButton aspect_checkbox
int64_t framecnt_t
Definition: types.h:76
LIBARDOUR_API RCConfiguration * Config
Definition: globals.cc:119
bool confirm_video_outfn(std::string, std::string docroot="")
Definition: amp.h:29
#define gui_context()
Definition: gui_thread.h:36
void update_progress(ARDOUR::framecnt_t, ARDOUR::framecnt_t)
common functions used for video-file im/export
Gtk::CheckButton debug_checkbox
bool transcode(std::string, const int outwidth=0, const int outheight=0, const int kbitps=0)
std::string video_dest_dir(const std::string, const std::string)
FFAudioStreams get_audio()
bool extract_audio(std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream=0)
PBD::Signal0< void > StartNextStage
void set_debug(bool onoff)
std::vector< FFAudioStream > FFAudioStreams
Definition: debug.h:30
virtual void set_session(ARDOUR::Session *)
PBD::Signal0< void > Finished
Gtk::ComboBoxText scale_combo
TranscodeVideoDialog(ARDOUR::Session *, std::string)
const SessionDirectory & session_directory() const
Definition: session.h:182
Gtk::CheckButton bitrate_checkbox
std::string get_codec()
VtlTranscodeOption
ARDOUR::Session * _session
Gtk::ComboBoxText video_combo
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
double atof(const string &s)
Definition: convert.cc:158
ARDOUR::framecnt_t get_duration()
VtlTranscodeOption import_option()
void popup_error(const std::string &text)
Definition: gtk_ui.cc:665