/* * Copyright (C) 2013-2017 Paul Davis * Copyright (C) 2013-2019 Robin Gareus * Copyright (C) 2015 André Nusser * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "pbd/gstdio_compat.h" #include "ardour/profile.h" #include "ardour/session.h" #include "ardour/session_directory.h" #include "ardour/template_utils.h" #include "ardour_ui.h" #include "gtkmm2ext/utils.h" #include "gui_thread.h" #include "pbd/convert.h" #include "pbd/error.h" #include "opts.h" #include "transcode_video_dialog.h" #include "utils_videotl.h" #include "pbd/i18n.h" using namespace Gtk; using namespace std; using namespace PBD; using namespace ARDOUR; using namespace VideoUtils; TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile) : ArdourDialog (_("Transcode/Import Video File ")) , infn (infile) , path_label (_("Output File:"), Gtk::ALIGN_START) , browse_button (_("Browse")) , transcode_button (_("OK")) , abort_button (_("Abort")) , progress_label () , aspect_checkbox (_("Height = ")) , height_adjustment (128, 0, 1920, 1, 16, 0) , height_spinner (height_adjustment) , ltc_detect (_("Extract LTC from audio and align video")) , bitrate_checkbox (_("Manual Override")) , bitrate_adjustment (2000, 500, 10000, 10, 100, 0) , bitrate_spinner (bitrate_adjustment) , debug_checkbox (_("Debug Mode: Print ffmpeg command and output to stdout.")) { set_session (s); transcoder = new TranscodeFfmpeg (infile); audiofile = ""; pending_audio_extract = false; aborted = false; set_name ("TranscodeVideoDialog"); set_modal (true); set_skip_taskbar_hint (true); set_resizable (false); Gtk::Label* l; vbox = manage (new VBox); VBox* options_box = manage (new VBox); HBox* path_hbox = manage (new HBox); int w = 0, h = 0; m_aspect = 16.0 / 9.0; TranscodeFfmpeg::FFAudioStreams as; path_hbox->pack_start (path_label, false, false, 3); path_hbox->pack_start (path_entry, true, true, 3); path_hbox->pack_start (browse_button, false, false, 3); height_spinner.set_sensitive (false); bitrate_spinner.set_sensitive (false); std::string dstdir = video_dest_dir (_session->session_directory ().video_path (), video_get_docroot (Config)); std::string dstfn = video_dest_file (dstdir, infile); path_entry.set_width_chars (38); path_entry.set_text (dstfn); l = manage (new Label (_("File Information"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); l->set_use_markup (); options_box->pack_start (*l, false, true, 4); bool ffok = false; if (!transcoder->ffexec_ok ()) { l = manage (new Label (_("ffmpeg installation was not found. Video Import is not possible. See the Log window for more information."), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); l->set_line_wrap (); options_box->pack_start (*l, false, true, 4); aspect_checkbox.set_sensitive (false); bitrate_checkbox.set_sensitive (false); } else if (!transcoder->probe_ok ()) { 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_START, Gtk::ALIGN_CENTER, false)); options_box->pack_start (*l, false, true, 4); aspect_checkbox.set_sensitive (false); bitrate_checkbox.set_sensitive (false); } else { w = transcoder->get_width (); h = transcoder->get_height (); as = transcoder->get_audio (); m_aspect = transcoder->get_aspect (); if (w > 0 && h > 0 && transcoder->get_fps () > 0 && transcoder->get_duration () > 0) { ffok = true; } Table* t = manage (new Table (4, 2)); t->set_spacings (4); options_box->pack_start (*t, true, true, 4); l = manage (new Label (_("FPS:"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 0, 1, 0, 1); l = manage (new Label (_("Duration:"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 2, 3, 0, 1); l = manage (new Label (_("Codec:"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 0, 1, 1, 2); l = manage (new Label (_("Geometry:"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 2, 3, 1, 2); std::ostringstream osstream; osstream << transcoder->get_fps (); l = manage (new Label (osstream.str (), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 1, 2, 0, 1); osstream.str (""); osstream << w << "x" << h; l = manage (new Label (osstream.str (), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 3, 4, 1, 2); osstream.str (""); if (transcoder->get_duration () == 0 || transcoder->get_fps () == 0) { osstream << _("??"); } else { unsigned long sec = transcoder->get_duration () / transcoder->get_fps (); osstream << setfill ('0') << setw (2); osstream << (sec / 3600) << ":"; osstream << setfill ('0') << setw (2); osstream << ((sec / 60) % 60) << ":"; osstream << setfill ('0') << setw (2); osstream << (sec % 60) << ":"; osstream << setfill ('0') << setw (2); osstream << (transcoder->get_duration () % (int)floor (transcoder->get_fps ())); } l = manage (new Label (osstream.str (), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 3, 4, 0, 1); osstream.str (""); osstream << transcoder->get_codec (); l = manage (new Label (osstream.str (), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 1, 2, 1, 2); } l = manage (new Label (_("Import Settings"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); l->set_use_markup (); options_box->pack_start (*l, false, true, 4); if (ffok) { video_combo.append (_("Reference from Current Location (Previously Transcoded Files Only)")); video_combo.append (_("Import/Transcode Video to Session")); video_combo.set_active (1); if (as.size () > 0) { video_combo.append (_("Do Not Import Video (Audio Import Only)")); audio_combo.set_sensitive (true); } else { audio_combo.set_sensitive (false); } video_combo.set_sensitive (true); transcode_button.set_sensitive (true); path_entry.set_sensitive (true); browse_button.set_sensitive (true); } else if (as.size () > 0) { video_combo.append (_("Do Not Import Video (Audio Import Only)")); video_combo.set_active (0); path_entry.set_text (""); video_combo.set_sensitive (false); audio_combo.set_sensitive (true); transcode_button.set_sensitive (true); path_entry.set_sensitive (false); browse_button.set_sensitive (false); } else { video_combo.append (_("Do Not Import Video")); video_combo.set_active (0); path_entry.set_text (""); video_combo.set_sensitive (false); audio_combo.set_sensitive (false); transcode_button.set_sensitive (false); path_entry.set_sensitive (false); browse_button.set_sensitive (false); } options_box->pack_start (video_combo, false, false, 4); Table* t = manage (new Table (4, 4)); t->set_spacings (4); options_box->pack_start (*t, true, true, 4); l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 0, 1, 0, 1); t->attach (scale_combo, 1, 2, 0, 1); t->attach (aspect_checkbox, 2, 3, 0, 1); t->attach (height_spinner, 3, 4, 0, 1); scale_combo.append (_("Original Width")); if (w > 1920) { scale_combo.append ("1920 (hd1080)"); } if (w > 1408) { scale_combo.append ("1408 (16cif)"); } if (w > 1280) { scale_combo.append ("1280 (sxga, hd720)"); } if (w > 1024) { scale_combo.append ("1024 (xga)"); } if (w > 852) { scale_combo.append (" 852 (hd480)"); } if (w > 768) { scale_combo.append (" 768 (PAL)"); } if (w > 720) { scale_combo.append (" 720 (PAL)"); } if (w > 640) { scale_combo.append (" 640 (vga, ega)"); } if (w > 352) { scale_combo.append (" 352 (cif)"); } if (w > 320) { scale_combo.append (" 320 (cga, qvga)"); } if (w > 176) { scale_combo.append (" 176 (qcif)"); } scale_combo.set_active (0); height_spinner.set_value (h); l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 0, 1, 1, 2); t->attach (bitrate_checkbox, 2, 3, 1, 2); t->attach (bitrate_spinner, 3, 4, 1, 2); l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, false)); t->attach (*l, 0, 1, 2, 3); t->attach (audio_combo, 1, 4, 2, 3); t->attach (ltc_detect, 1, 4, 3, 4); if (as.size () == 0) { audio_combo.append (_("No Audio Track Present")); audio_combo.set_sensitive (false); } else { audio_combo.append (_("Do Not Extract Audio")); for (TranscodeFfmpeg::FFAudioStreams::iterator it = as.begin (); it < as.end (); ++it) { audio_combo.append ((*it).name); } } audio_combo.set_active (0); ltc_detect.set_sensitive (false); options_box->pack_start (debug_checkbox, false, true, 4); vbox->pack_start (*path_hbox, false, false); vbox->pack_start (*options_box, false, true); get_vbox ()->set_spacing (4); get_vbox ()->pack_start (*vbox, false, false); progress_box = manage (new VBox); progress_box->set_spacing (6); progress_box->pack_start (progress_label, false, false); progress_box->pack_start (pbar, false, false); progress_box->pack_start (abort_button, false, false); get_vbox ()->pack_start (*progress_box, false, false); browse_button.signal_clicked ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog)); transcode_button.signal_clicked ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode)); abort_button.signal_clicked ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked)); video_combo.signal_changed ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed)); audio_combo.signal_changed ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed)); scale_combo.signal_changed ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed)); aspect_checkbox.signal_toggled ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled)); height_spinner.signal_changed ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate)); bitrate_checkbox.signal_toggled ().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled)); update_bitrate (); cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL); get_action_area ()->pack_start (transcode_button, false, false); show_all_children (); progress_box->hide (); } TranscodeVideoDialog::~TranscodeVideoDialog () { delete transcoder; } void TranscodeVideoDialog::on_show () { Dialog::on_show (); } void TranscodeVideoDialog::abort_clicked () { aborted = true; transcoder->cancel (); } void TranscodeVideoDialog::update_progress (samplecnt_t c, samplecnt_t a) { if (a == 0 || c > a) { pbar.set_pulse_step (.5); pbar.pulse (); return; } pbar.set_fraction ((double)c / (double)a); } void TranscodeVideoDialog::finished (int status) { if (aborted || status != 0) { if (!aborted) { ARDOUR_UI::instance ()->popup_error (_("Video transcoding failed.")); } ::g_unlink (path_entry.get_text ().c_str ()); if (!audiofile.empty ()) { ::g_unlink (audiofile.c_str ()); } Gtk::Dialog::response (RESPONSE_CANCEL); } else { if (pending_audio_extract) { StartNextStage (); /* EMIT SIGNAL */ } else { Gtk::Dialog::response (RESPONSE_ACCEPT); } } } void TranscodeVideoDialog::launch_audioonly () { if (audio_combo.get_active_row_number () == 0) { finished (0); return; } dialog_progress_mode (); if (debug_checkbox.get_active ()) { transcoder->set_debug (true); } transcoder->Progress.connect (*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress, this, _1, _2), gui_context ()); transcoder->Finished.connect (*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this, _1), gui_context ()); launch_extract (); } void TranscodeVideoDialog::launch_extract () { audiofile = path_entry.get_text () + ".wav"; /* TODO: mktemp */ pending_audio_extract = false; aborted = false; int audio_stream = audio_combo.get_active_row_number () - 1; progress_label.set_text (_("Extracting Audio")); if (!transcoder->extract_audio (audiofile, _session->nominal_sample_rate (), audio_stream)) { ARDOUR_UI::instance ()->popup_error (_("Audio Extraction Failed.")); audiofile = ""; Gtk::Dialog::response (RESPONSE_CANCEL); return; } } void TranscodeVideoDialog::dialog_progress_mode () { vbox->hide (); cancel_button->hide (); transcode_button.hide (); pbar.set_size_request (300, -1); progress_box->show (); } void TranscodeVideoDialog::launch_transcode () { if (video_combo.get_active_row_number () != 1) { launch_audioonly (); return; } std::string outfn = path_entry.get_text (); if (!confirm_video_outfn (*this, outfn, video_get_docroot (Config))) return; progress_label.set_text (_("Transcoding Video")); dialog_progress_mode (); if (debug_checkbox.get_active ()) { transcoder->set_debug (true); } aborted = false; if (audio_combo.get_active_row_number () != 0) { pending_audio_extract = true; StartNextStage.connect (*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract, this), gui_context ()); } int scale_width, scale_height, bitrate; if (scale_combo.get_active_row_number () == 0) { scale_width = 0; } else { scale_width = atoi (scale_combo.get_active_text ()); } if (!aspect_checkbox.get_active ()) { scale_height = 0; } else { scale_height = (int)floor (height_spinner.get_value ()); } if (bitrate_checkbox.get_active ()) { bitrate = (int)floor (bitrate_spinner.get_value ()); } else { bitrate = 0; } transcoder->Progress.connect (*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress, this, _1, _2), gui_context ()); transcoder->Finished.connect (*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this, _1), gui_context ()); if (!transcoder->transcode (outfn, scale_width, scale_height, bitrate)) { ARDOUR_UI::instance ()->popup_error (_("Transcoding Failed.")); Gtk::Dialog::response (RESPONSE_CANCEL); return; } } void TranscodeVideoDialog::video_combo_changed () { const int i = video_combo.get_active_row_number (); if (i != 1) { scale_combo.set_sensitive (false); aspect_checkbox.set_sensitive (false); height_spinner.set_sensitive (false); bitrate_checkbox.set_sensitive (false); bitrate_spinner.set_sensitive (false); } else { scale_combo.set_sensitive (true); aspect_checkbox.set_sensitive (true); height_spinner.set_sensitive (true); bitrate_checkbox.set_sensitive (true); bitrate_spinner.set_sensitive (true); } if (i == 2 && audio_combo.get_active_row_number () == 0) { audio_combo.set_active (1); } else { //update LTC option sensitivity audio_combo_changed (); } } void TranscodeVideoDialog::audio_combo_changed () { if (video_combo.get_active_row_number () == 2 && audio_combo.get_active_row_number () == 0) { audio_combo.set_active (1); ltc_detect.set_sensitive (false); ltc_detect.set_active (false); } if (video_combo.get_active_row_number () != 2 && audio_combo.get_active_row_number () > 0) { ltc_detect.set_sensitive (true); } else { ltc_detect.set_sensitive (false); ltc_detect.set_active (false); } } void TranscodeVideoDialog::scale_combo_changed () { if (!aspect_checkbox.get_active ()) { int h; if (scale_combo.get_active_row_number () == 0) { h = transcoder->get_height (); } else { h = floor (atof (scale_combo.get_active_text ()) / m_aspect); } height_spinner.set_value (h); } update_bitrate (); } void TranscodeVideoDialog::aspect_checkbox_toggled () { height_spinner.set_sensitive (aspect_checkbox.get_active ()); scale_combo_changed (); } void TranscodeVideoDialog::bitrate_checkbox_toggled () { bitrate_spinner.set_sensitive (bitrate_checkbox.get_active ()); if (!bitrate_checkbox.get_active ()) { update_bitrate (); } } void TranscodeVideoDialog::update_bitrate () { double br = .7; /* avg quality - bits per pixel */ if (bitrate_checkbox.get_active () || !transcoder->probe_ok ()) { return; } br *= transcoder->get_fps (); br *= height_spinner.get_value (); if (scale_combo.get_active_row_number () == 0) { br *= transcoder->get_width (); } else { br *= atof (scale_combo.get_active_text ()); } if (br != 0) { bitrate_spinner.set_value (floor (br / 10000.0) * 10); } } void TranscodeVideoDialog::open_browse_dialog () { Gtk::FileChooserDialog dialog (_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE); dialog.set_filename (path_entry.get_text ()); Gtkmm2ext::add_volume_shortcuts (dialog); dialog.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK); int result = dialog.run (); if (result == Gtk::RESPONSE_OK) { std::string filename = dialog.get_filename (); if (filename.length ()) { path_entry.set_text (filename); } } } enum VtlTranscodeOption TranscodeVideoDialog::import_option () { int i = video_combo.get_active_row_number (); return static_cast (i); }