2 Copyright (C) 2010,2013 Paul Davis
3 Author: Robin Gareus <robin@gareus.org>
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.
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.
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.
26 #include <sys/types.h>
30 #include <sigc++/bind.h>
32 #include <pbd/gstdio_compat.h>
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"
45 #include "transcode_video_dialog.h"
46 #include "utils_videotl.h"
52 using namespace ARDOUR;
53 using namespace VideoUtils;
55 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
56 : ArdourDialog (_("Transcode/Import Video File "))
58 , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
59 , browse_button (_("Browse"))
60 , transcode_button (_("OK"))
61 , abort_button (_("Abort"))
63 , aspect_checkbox (_("Height = "))
64 , height_adjustment (128, 0, 1920, 1, 16, 0)
65 , height_spinner (height_adjustment)
66 , ltc_detect (_("Extract LTC from audio and align video"))
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."))
76 transcoder = new TranscodeFfmpeg(infile);
78 pending_audio_extract = false;
81 set_name ("TranscodeVideoDialog");
83 set_skip_taskbar_hint (true);
84 set_resizable (false);
87 vbox = manage (new VBox);
88 VBox* options_box = manage (new VBox);
89 HBox* path_hbox = manage (new HBox);
93 TranscodeFfmpeg::FFAudioStreams as; as.clear();
95 path_hbox->pack_start (path_label, false, false, 3);
96 path_hbox->pack_start (path_entry, true, true, 3);
97 path_hbox->pack_start (browse_button, false, false, 3);
98 browse_button.set_name ("PaddedButton");
100 path_entry.set_width_chars(38);
101 height_spinner.set_sensitive(false);
102 bitrate_spinner.set_sensitive(false);
104 std::string dstdir = video_dest_dir(_session->session_directory().video_path(), video_get_docroot(Config));
105 std::string dstfn = video_dest_file(dstdir, infile);
106 path_entry.set_text (dstfn);
108 l = manage (new Label (_("<b>File Information</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
109 l->set_use_markup ();
110 options_box->pack_start (*l, false, true, 4);
113 if (!transcoder->ffexec_ok()) {
114 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 options_box->pack_start (*l, false, true, 4);
117 aspect_checkbox.set_sensitive(false);
118 bitrate_checkbox.set_sensitive(false);
120 else if (!transcoder->probe_ok()) {
121 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));
122 options_box->pack_start (*l, false, true, 4);
123 aspect_checkbox.set_sensitive(false);
124 bitrate_checkbox.set_sensitive(false);
126 w = transcoder->get_width();
127 h = transcoder->get_height();
128 as = transcoder->get_audio();
129 m_aspect = transcoder->get_aspect();
131 if (w > 0 && h > 0 && transcoder->get_fps() > 0 && transcoder->get_duration() > 0) {
135 Table* t = manage (new Table (4, 2));
137 options_box->pack_start (*t, true, true, 4);
138 l = manage (new Label (_("FPS:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
139 t->attach (*l, 0, 1, 0, 1);
140 l = manage (new Label (_("Duration:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
141 t->attach (*l, 2, 3, 0, 1);
142 l = manage (new Label (_("Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
143 t->attach (*l, 0, 1, 1, 2);
144 l = manage (new Label (_("Geometry:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
145 t->attach (*l, 2, 3, 1, 2);
147 std::ostringstream osstream;
148 osstream << transcoder->get_fps();
149 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
150 t->attach (*l, 1, 2, 0, 1);
153 osstream << w << "x" << h;
154 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
155 t->attach (*l, 3, 4, 1, 2);
158 if (transcoder->get_duration() == 0 || transcoder->get_fps() == 0) {
161 unsigned long sec = transcoder->get_duration() / transcoder->get_fps();
162 osstream << setfill('0') << setw(2);
163 osstream << (sec / 3600) << ":";
164 osstream << setfill('0') << setw(2);
165 osstream << ((sec /60 )%60) << ":";
166 osstream << setfill('0') << setw(2);
167 osstream << (sec%60) << ":";
168 osstream << setfill('0') << setw(2);
169 osstream << (transcoder->get_duration() % (int) floor(transcoder->get_fps()));
171 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
172 t->attach (*l, 3, 4, 0, 1);
175 osstream << transcoder->get_codec();
176 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
177 t->attach (*l, 1, 2, 1, 2);
180 l = manage (new Label (_("<b>Import Settings</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
181 l->set_use_markup ();
182 options_box->pack_start (*l, false, true, 4);
184 video_combo.set_name ("PaddedButton");
187 video_combo.append_text(_("Reference From Current Location (Previously Transcoded Files Only)"));
188 video_combo.append_text(_("Import/Transcode Video to Session"));
189 video_combo.set_active(1);
191 video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
192 audio_combo.set_sensitive(true);
194 audio_combo.set_sensitive(false);
196 video_combo.set_sensitive(true);
197 transcode_button.set_sensitive(true);
198 path_entry.set_sensitive (true);
199 browse_button.set_sensitive (true);
200 } else if (as.size() > 0) {
201 video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
202 video_combo.set_active(0);
203 path_entry.set_text ("");
205 video_combo.set_sensitive(false);
206 audio_combo.set_sensitive(true);
207 transcode_button.set_sensitive(true);
208 path_entry.set_sensitive (false);
209 browse_button.set_sensitive (false);
211 video_combo.append_text(_("Do Not Import Video"));
212 video_combo.set_active(0);
213 path_entry.set_text ("");
214 video_combo.set_sensitive(false);
215 audio_combo.set_sensitive(false);
216 transcode_button.set_sensitive(false);
217 path_entry.set_sensitive (false);
218 browse_button.set_sensitive (false);
221 options_box->pack_start (video_combo, false, false, 4);
223 Table* t = manage (new Table (4, 4));
225 options_box->pack_start (*t, true, true, 4);
227 l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
228 t->attach (*l, 0, 1, 0, 1);
229 scale_combo.set_name ("PaddedButton");
230 t->attach (scale_combo, 1, 2, 0, 1);
231 t->attach (aspect_checkbox, 2, 3, 0, 1);
232 t->attach (height_spinner, 3, 4, 0, 1);
234 scale_combo.append_text(_("Original Width"));
235 if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
236 if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
237 if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
238 if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
239 if (w > 852) { scale_combo.append_text(" 852 (hd480)"); }
240 if (w > 768) { scale_combo.append_text(" 768 (PAL)"); }
241 if (w > 720) { scale_combo.append_text(" 720 (PAL)"); }
242 if (w > 640) { scale_combo.append_text(" 640 (vga, ega)"); }
243 if (w > 352) { scale_combo.append_text(" 352 (cif)"); }
244 if (w > 320) { scale_combo.append_text(" 320 (cga, qvga)"); }
245 if (w > 176) { scale_combo.append_text(" 176 (qcif)"); }
246 scale_combo.set_active(0);
247 height_spinner.set_value(h);
249 l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
250 t->attach (*l, 0, 1, 1, 2);
251 t->attach (bitrate_checkbox, 2, 3, 1, 2);
252 t->attach (bitrate_spinner, 3, 4, 1, 2);
254 l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
255 t->attach (*l, 0, 1, 2, 3);
256 audio_combo.set_name ("PaddedButton");
257 t->attach (audio_combo, 1, 4, 2, 3);
258 t->attach (ltc_detect, 1, 4, 3, 4);
259 if (as.size() == 0) {
260 audio_combo.append_text(_("No Audio Track Present"));
261 audio_combo.set_sensitive(false);
263 audio_combo.append_text(_("Do Not Extract Audio"));
264 for (TranscodeFfmpeg::FFAudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
265 audio_combo.append_text((*it).name);
268 audio_combo.set_active(0);
269 ltc_detect.set_sensitive (false);
271 #if 1 /* tentative debug mode */
272 options_box->pack_start (debug_checkbox, false, true, 4);
275 vbox->pack_start (*path_hbox, false, false);
276 vbox->pack_start (*options_box, false, true);
278 get_vbox()->set_spacing (4);
279 get_vbox()->pack_start (*vbox, false, false);
281 progress_box = manage (new VBox);
282 progress_box->pack_start (progress_label, false, false);
283 progress_box->pack_start (pbar, false, false);
284 progress_box->pack_start (abort_button, false, false);
285 get_vbox()->pack_start (*progress_box, false, false);
287 browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
288 transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
289 abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
291 video_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed));
292 audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
293 scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
294 aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
295 height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
296 bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
300 cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
301 get_action_area()->pack_start (transcode_button, false, false);
302 show_all_children ();
303 progress_box->hide();
306 TranscodeVideoDialog::~TranscodeVideoDialog ()
312 TranscodeVideoDialog::on_show ()
318 TranscodeVideoDialog::abort_clicked ()
321 transcoder->cancel();
325 TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
327 if (a == 0 || c > a) {
328 pbar.set_pulse_step(.5);
332 pbar.set_fraction ((double)c / (double) a);
336 TranscodeVideoDialog::finished ()
339 ::g_unlink(path_entry.get_text().c_str());
340 if (!audiofile.empty()) {
341 ::g_unlink(audiofile.c_str());
343 Gtk::Dialog::response(RESPONSE_CANCEL);
345 if (pending_audio_extract) {
348 Gtk::Dialog::response(RESPONSE_ACCEPT);
354 TranscodeVideoDialog::launch_audioonly ()
356 if (audio_combo.get_active_row_number() == 0) {
360 dialog_progress_mode();
361 #if 1 /* tentative debug mode */
362 if (debug_checkbox.get_active()) {
363 transcoder->set_debug(true);
366 transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
367 transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
372 TranscodeVideoDialog::launch_extract ()
374 audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp */
376 pending_audio_extract = false;
378 audio_stream = audio_combo.get_active_row_number() -1;
379 progress_label.set_text (_("Extracting Audio.."));
381 if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
382 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
384 Gtk::Dialog::response(RESPONSE_CANCEL);
390 TranscodeVideoDialog::dialog_progress_mode ()
393 cancel_button->hide();
394 transcode_button.hide();
395 pbar.set_size_request(300,-1);
396 progress_box->show();
400 TranscodeVideoDialog::launch_transcode ()
402 if (video_combo.get_active_row_number() != 1) {
406 std::string outfn = path_entry.get_text();
407 if (!confirm_video_outfn(outfn, video_get_docroot(Config))) return;
408 progress_label.set_text (_("Transcoding Video.."));
409 dialog_progress_mode();
410 #if 1 /* tentative debug mode */
411 if (debug_checkbox.get_active()) {
412 transcoder->set_debug(true);
417 if (audio_combo.get_active_row_number() != 0) {
418 pending_audio_extract = true;
419 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
422 int scale_width, scale_height, bitrate;
423 if (scale_combo.get_active_row_number() == 0 ) {
426 scale_width = atoi(scale_combo.get_active_text());
428 if (!aspect_checkbox.get_active()) {
431 scale_height = (int) floor(height_spinner.get_value());
433 if (bitrate_checkbox.get_active() ){
434 bitrate = (int) floor(bitrate_spinner.get_value());
439 transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
440 transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
441 if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
442 ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
443 Gtk::Dialog::response(RESPONSE_CANCEL);
449 TranscodeVideoDialog::video_combo_changed ()
451 const int i = video_combo.get_active_row_number();
453 scale_combo.set_sensitive(false);
454 aspect_checkbox.set_sensitive(false);
455 height_spinner.set_sensitive(false);
456 bitrate_checkbox.set_sensitive(false);
457 bitrate_spinner.set_sensitive(false);
459 scale_combo.set_sensitive(true);
460 aspect_checkbox.set_sensitive(true);
461 height_spinner.set_sensitive(true);
462 bitrate_checkbox.set_sensitive(true);
463 bitrate_spinner.set_sensitive(true);
465 if (i == 2 && audio_combo.get_active_row_number() == 0) {
466 audio_combo.set_active(1);
468 //update LTC option sensitivity
469 audio_combo_changed ();
474 TranscodeVideoDialog::audio_combo_changed ()
476 if (video_combo.get_active_row_number() == 2
477 && audio_combo.get_active_row_number() == 0)
479 audio_combo.set_active(1);
480 ltc_detect.set_sensitive (false);
481 ltc_detect.set_active (false);
484 if (video_combo.get_active_row_number() != 2
485 && audio_combo.get_active_row_number() > 0)
487 ltc_detect.set_sensitive (true);
489 ltc_detect.set_sensitive (false);
490 ltc_detect.set_active (false);
495 TranscodeVideoDialog::scale_combo_changed ()
497 if (!aspect_checkbox.get_active()) {
499 if (scale_combo.get_active_row_number() == 0 ) {
500 h = transcoder->get_height();
502 h = floor(atof(scale_combo.get_active_text()) / m_aspect);
504 height_spinner.set_value(h);
510 TranscodeVideoDialog::aspect_checkbox_toggled ()
512 height_spinner.set_sensitive(aspect_checkbox.get_active());
513 scale_combo_changed();
517 TranscodeVideoDialog::bitrate_checkbox_toggled ()
519 bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
520 if (!bitrate_checkbox.get_active()) {
526 TranscodeVideoDialog::update_bitrate ()
528 double br = .7; /* avg quality - bits per pixel */
529 if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
530 br *= transcoder->get_fps();
531 br *= height_spinner.get_value();
533 if (scale_combo.get_active_row_number() == 0 ) {
534 br *= transcoder->get_width();
536 br *= atof(scale_combo.get_active_text());
539 bitrate_spinner.set_value(floor(br/10000.0)*10);
544 TranscodeVideoDialog::open_browse_dialog ()
546 Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
547 dialog.set_filename (path_entry.get_text());
549 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
550 dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
552 int result = dialog.run();
554 if (result == Gtk::RESPONSE_OK) {
555 std::string filename = dialog.get_filename();
557 if (filename.length()) {
558 path_entry.set_text (filename);
563 enum VtlTranscodeOption
564 TranscodeVideoDialog::import_option() {
565 int i = video_combo.get_active_row_number();
566 return static_cast<VtlTranscodeOption>(i);