2 Copyright (C) 2010 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.
20 #ifdef WITH_VIDEOTIMELINE
28 #include <sys/types.h>
32 #include <sigc++/bind.h>
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"
47 #include "transcode_video_dialog.h"
48 #include "video_copy_dialog.h"
49 #include "utils_videotl.h"
55 using namespace ARDOUR;
57 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
58 : ArdourDialog (_("Transcode/Import Video File "))
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"))
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."))
79 transcoder = new TranscodeFfmpeg(infile);
81 pending_audio_extract = false;
82 pending_copy_file = false;
84 set_name ("TranscodeVideoDialog");
85 set_position (Gtk::WIN_POS_MOUSE);
87 set_skip_taskbar_hint (true);
88 set_resizable (false);
91 vbox = manage (new VBox);
92 VBox* options_box = manage (new VBox);
93 HBox* path_hbox = manage (new HBox);
97 AudioStreams as; as.clear();
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");
104 path_entry.set_width_chars(38);
105 height_spinner.set_sensitive(false);
106 bitrate_spinner.set_sensitive(false);
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);
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);
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));
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);
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);
132 w = transcoder->get_width();
133 h = transcoder->get_height();
134 as = transcoder->get_audio();
135 m_aspect = transcoder->get_aspect();
137 Table* t = manage (new Table (4, 2));
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);
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);
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);
160 if (transcoder->get_duration() == 0 || transcoder->get_fps() == 0) {
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()));
173 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
174 t->attach (*l, 3, 4, 0, 1);
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);
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);
186 Table* t = manage (new Table (4, 3));
188 options_box->pack_start (*t, true, true, 4);
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);
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);
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);
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");
223 for (AudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
224 audio_combo.append_text((*it).name);
227 audio_combo.set_active(0);
229 #if 1 /* tentative debug mode */
230 options_box->pack_start (debug_checkbox, false, true, 4);
233 vbox->pack_start (*path_hbox, false, false);
234 vbox->pack_start (*options_box, false, true);
236 get_vbox()->set_spacing (4);
237 get_vbox()->pack_start (*vbox, false, false);
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);
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));
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));
259 audio_button.set_sensitive(false);
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();
269 TranscodeVideoDialog::~TranscodeVideoDialog ()
275 TranscodeVideoDialog::on_show ()
281 TranscodeVideoDialog::abort_clicked ()
284 transcoder->cancel();
288 TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
290 if (a == 0 || c > a) {
291 pbar.set_pulse_step(.5);
295 pbar.set_fraction ((double)c / (double) a);
299 TranscodeVideoDialog::finished ()
302 unlink(path_entry.get_text().c_str());
303 if (!audiofile.empty()) {
304 unlink(audiofile.c_str());
306 Gtk::Dialog::response(RESPONSE_CANCEL);
308 if (pending_audio_extract || pending_copy_file) {
311 Gtk::Dialog::response(RESPONSE_ACCEPT);
317 TranscodeVideoDialog::prepare_copy ()
319 dialog_progress_mode();
320 #if 1 /* tentative debug mode */
321 if (debug_checkbox.get_active()) {
322 transcoder->set_debug(true);
326 if (audio_combo.get_active_row_number() == 0) {
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());
339 TranscodeVideoDialog::launch_audioonly ()
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);
348 if (audio_combo.get_active_row_number() == 0) {
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());
357 TranscodeVideoDialog::launch_copy ()
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);
370 TranscodeVideoDialog::launch_extract ()
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() ?!
377 pending_audio_extract = false;
379 audio_stream = audio_combo.get_active_row_number() -1;
380 progress_label.set_text (_("Extracting Audio.."));
382 if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
383 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
385 Gtk::Dialog::response(RESPONSE_CANCEL);
391 TranscodeVideoDialog::dialog_progress_mode ()
394 cancel_button->hide();
396 transcode_button.hide();
397 pbar.set_size_request(300,-1);
398 progress_box->show();
402 TranscodeVideoDialog::launch_transcode ()
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);
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());
420 int scale_width, scale_height, bitrate;
421 if (scale_combo.get_active_row_number() == 0 ) {
424 scale_width = atoi(scale_combo.get_active_text().c_str());
426 if (!aspect_checkbox.get_active()) {
429 scale_height = (int) floor(height_spinner.get_value());
431 if (bitrate_checkbox.get_active() ){
432 bitrate = (int) floor(bitrate_spinner.get_value());
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);
447 TranscodeVideoDialog::audio_combo_changed ()
449 bool use_audio = audio_combo.get_active_row_number() != 0;
450 audio_button.set_sensitive(use_audio);
452 copy_button.set_label(_("Copy File And\nExtract Audio"));
454 copy_button.set_label(_("Copy Video\nFile Only"));
459 TranscodeVideoDialog::scale_combo_changed ()
462 if (!aspect_checkbox.get_active()) {
464 if (scale_combo.get_active_row_number() == 0 ) {
465 h = transcoder->get_height();
467 h = floor(atof(scale_combo.get_active_text().c_str()) / m_aspect);
469 height_spinner.set_value(h);
474 TranscodeVideoDialog::aspect_checkbox_toggled ()
476 height_spinner.set_sensitive(aspect_checkbox.get_active());
477 scale_combo_changed();
481 TranscodeVideoDialog::bitrate_checkbox_toggled ()
483 bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
484 if (!bitrate_checkbox.get_active()) {
490 TranscodeVideoDialog::update_bitrate ()
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();
499 br *= atof(scale_combo.get_active_text().c_str());
501 bitrate_spinner.set_value(floor(br/10000.0)*10);
505 TranscodeVideoDialog::open_browse_dialog ()
507 Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
508 dialog.set_filename (path_entry.get_text());
510 dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
511 dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
513 int result = dialog.run();
515 if (result == Gtk::RESPONSE_OK) {
516 std::string filename = dialog.get_filename();
518 if (filename.length()) {
519 path_entry.set_text (filename);
524 #endif /* WITH_VIDEOTIMELINE */