/* * Copyright (C) 2022 Robin Gareus * * 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 "pbd/openuri.h" #include "ardour/export_channel_configuration.h" #include "ardour/export_filename.h" #include "ardour/export_preset.h" #include "ardour/export_profile_manager.h" #include "ardour/export_status.h" #include "ardour/export_timespan.h" #include "ardour/profile.h" #include "ardour/session_directory.h" #include "ardour/surround_return.h" #include "nag.h" #include "simple_export_dialog.h" #include "pbd/i18n.h" using namespace std; using namespace ARDOUR; using namespace PBD; using namespace Gtk; SimpleExportDialog::SimpleExportDialog (PublicEditor& editor, bool vapor_export) : ARDOUR::SimpleExport () , ArdourDialog (vapor_export ? _("Surround Master Export") : _("Quick Audio Export"), true, false) , _editor (editor) , _eps (true) , _vapor_export (vapor_export) { if (_eps.the_combo ().get_parent ()) { _eps.the_combo ().get_parent ()->remove (_eps.the_combo ()); } _range_list = ListStore::create (_range_cols); _range_combo.set_model (_range_list); _range_combo.pack_start (_range_cols.label); Table* t = manage (new Table); int r = 0; t->set_spacings (4); #define LBL(TXT) *manage (new Label (_(TXT), ALIGN_END)) /* clang-format off */ t->attach (LBL ("Format preset:"), 0, 1, r, r + 1, FILL, SHRINK, 0, 0); if (_vapor_export) { t->attach (LBL ("ADM BWF"), 1, 2, r, r + 1, EXPAND, SHRINK, 0, 0); } else { t->attach (_eps.the_combo (), 1, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0); } ++r; t->attach (LBL ("Export range:"), 0, 1, r, r + 1, FILL, SHRINK, 0, 0); t->attach (_range_combo, 1, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0); ++r; t->attach (LBL ("After export:"), 0, 1, r, r + 1, FILL, SHRINK, 0, 0); t->attach (_post_export_combo, 1, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0); ++r; t->attach (_error_label, 0, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0); ++r; t->attach (_progress_bar, 0, 2, r, r + 1, EXPAND | FILL, SHRINK, 0, 0); /* clang-format on */ #undef LBL _post_export_combo.append (_("Open the folder where files are exported")); _post_export_combo.append (_("Do nothing")); _post_export_combo.set_active (0); get_vbox ()->pack_start (*t, false, false); _cancel_button = add_button (Gtk::Stock::CANCEL, RESPONSE_CANCEL); _export_button = add_button (_("_Export"), RESPONSE_OK); _cancel_button->signal_clicked ().connect (sigc::mem_fun (*this, &SimpleExportDialog::close_dialog)); _export_button->signal_clicked ().connect (sigc::mem_fun (*this, &SimpleExportDialog::start_export)); _progress_bar.set_no_show_all (true); _error_label.set_no_show_all (true); _export_button->set_sensitive (false); _range_combo.set_sensitive (false); t->show_all (); } XMLNode& SimpleExportDialog::get_state () const { XMLNode* node = new XMLNode (X_("QuickExport")); node->set_property (X_("PresetUUID"), preset_uuid ()); node->set_property (X_("PostExport"), _post_export_combo.get_active_row_number ()); return *node; } void SimpleExportDialog::set_state (XMLNode const& node) { int post_export; std::string pset_uuid; if (node.get_property (X_("PresetUUID"), pset_uuid)) { set_preset (pset_uuid); } if (node.get_property (X_("PostExport"), post_export)) { _post_export_combo.set_active (post_export); } } void SimpleExportDialog::set_session (ARDOUR::Session* s) { SimpleExport::set_session (s); ArdourDialog::set_session (s); _range_list->clear (); _preset_cfg_connection.disconnect (); if (!s) { _export_button->set_sensitive (false); _range_combo.set_sensitive (false); return; } XMLNode* node = s->extra_xml (X_("QuickExport")); if (node) { set_state (*node); } _eps.set_manager (_manager); if (!check_outputs ()) { set_error ("Error: Session has no master bus"); return; } if (_vapor_export && (!s->surround_master () || !s->vapor_export_barrier ())) { set_error ("Error: Session has no exportable surround master."); return; } if (_vapor_export && (s->surround_master ()->surround_return ()->total_n_channels () > 128)) { set_error ("Error: ADM BWF files cannot contain more than 128 channels."); return; } /* check range */ Location* srl (s->locations ()->session_range_location ()); TimeSelection const& tsel (_editor.get_selection ().time); if (!tsel.empty ()) { TreeModel::Row row = *_range_list->append (); row[_range_cols.label] = _("Using time selection"); row[_range_cols.start] = tsel.start_sample (); row[_range_cols.end] = tsel.end_sample (); row[_range_cols.name] = string_compose (_("%1 (selection)"), SimpleExport::_session->snap_name ()); } if (srl) { TreeModel::Row row = *_range_list->append (); row[_range_cols.label] = _("Session start to session end"); // same text as ExportVideoDialog::apply_state row[_range_cols.start] = srl->start_sample (); row[_range_cols.end] = srl->end_sample (); row[_range_cols.name] = SimpleExport::_session->snap_name (); } struct LocationSorter { bool operator() (Location const* a, Location const* b) { return a->start_sample () < b->start_sample (); } }; Locations::LocationList ll (s->locations ()->list ()); ll.sort (LocationSorter ()); for (auto const& l : ll) { if (l->is_session_range () || !l->is_range_marker () || l->name ().empty ()) { continue; } TreeModel::Row row = *_range_list->append (); row[_range_cols.label] = l->name (); // string_compose (_("Range '%1'"), l->name ()); row[_range_cols.start] = l->start_sample (); row[_range_cols.end] = l->end_sample (); row[_range_cols.name] = string_compose (_("%1 - %2"), SimpleExport::_session->snap_name (), l->name ()); } if (_range_list->children ().size () == 0) { set_error ("Error: No valid range to export. Select a range or create session start/end markers"); return; } _range_combo.set_active (0); _range_combo.set_sensitive (true); _export_button->set_sensitive (true); _preset_cfg_connection = _eps.CriticalSelectionChanged.connect (sigc::mem_fun (*this, &SimpleExportDialog::check_manager)); } void SimpleExportDialog::check_manager () { bool ok = _manager && _manager->preset (); if (ok && _manager->get_formats ().empty ()) { ok = false; } if (ok) { /* check for NULL ExportFormatSpecPtr */ auto fms = _manager->get_formats (); for (auto const& fm : fms) { if (!fm->format) { ok = false; break; } } } _export_button->set_sensitive (ok); } void SimpleExportDialog::set_error (std::string const& err) { _export_button->set_sensitive (false); _range_combo.set_sensitive (false); _error_label.set_text (err); _error_label.show (); } void SimpleExportDialog::close_dialog () { if (_status->running ()) { _status->abort (); } } void SimpleExportDialog::start_export () { TreeModel::iterator r = _range_combo.get_active (); std::string range_name = (*r)[_range_cols.name]; set_range ((*r)[_range_cols.start], (*r)[_range_cols.end]); SimpleExport::set_name (range_name); if (_vapor_export) { if (range_name.empty ()) { range_name = SimpleExport::_session->snap_name (); } samplepos_t rend = (*r)[_range_cols.end]; samplepos_t t24h; Timecode::Time tc (SimpleExport::_session->timecode_frames_per_second ()); tc.hours = 24; SimpleExport::_session->timecode_to_sample (tc, t24h, false /* use_offset */, false /* use_subframes */); if (rend >= t24h) { hide (); std::string txt = _("Error: ADM/BWN files timecode cannot be past 24h."); Gtk::MessageDialog msg (txt, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); msg.run (); return; } /* Ensure timespan exists, see also SimpleExport::run_export */ auto ts = _manager->get_timespans (); assert (ts.size () == 1); assert (ts.front ()->timespans->size () < 2); if (ts.front ()->timespans->size () < 1) { ExportTimespanPtr timespan = _handler->add_timespan (); ts.front ()->timespans->push_back (timespan); } /* https://professional.dolby.com/siteassets/content-creation/dolby-atmos/dolby_atmos_renderer_guide.pdf * chapter 13.9, page 155 suggests .wav. * There may however already be a .wav file with the given name, so -adm.wav is used. */ std::string vapor = Glib::build_filename (SimpleExport::_session->session_directory ().export_path (), range_name + "-adm.wav"); _manager->get_timespans ().front ()->timespans->front ()->set_vapor (vapor); } SimpleExport::_session->add_extra_xml (get_state ()); _cancel_button->set_label (_("_Abort")); _export_button->set_sensitive (false); _progress_bar.set_fraction (0.0); _progress_bar.show (); #if 0 _eps.hide (); _range_combo.hide (); _post_export_combo,.hide (); #endif _progress_connection = Glib::signal_timeout ().connect (sigc::mem_fun (*this, &SimpleExportDialog::progress_timeout), 100); if (run_export ()) { hide (); if (_post_export_combo.get_active_row_number () == 0) { PBD::open_folder (folder ()); } if (!ARDOUR::Profile->get_mixbus ()) { NagScreen* ns = NagScreen::maybe_nag (_("Export")); if (ns) { ns->nag (); delete ns; } } } else if (!_status->aborted ()) { hide (); std::string txt = _("Export has been aborted due to an error!\nSee the Log window for details."); Gtk::MessageDialog msg (txt, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); msg.run (); } } bool SimpleExportDialog::progress_timeout () { std::string status_text; float progress = -1; switch (_status->active_job) { case ExportStatus::Exporting: status_text = string_compose (_("Exporting '%3' (timespan %1 of %2)"), _status->timespan, _status->total_timespans, _status->timespan_name); progress = ((float)_status->processed_samples_current_timespan) / _status->total_samples_current_timespan; break; case ExportStatus::Normalizing: status_text = string_compose (_("Normalizing '%3' (timespan %1 of %2)"), _status->timespan, _status->total_timespans, _status->timespan_name); progress = ((float)_status->current_postprocessing_cycle) / _status->total_postprocessing_cycles; break; case ExportStatus::Encoding: status_text = string_compose (_("Encoding '%3' (timespan %1 of %2)"), _status->timespan, _status->total_timespans, _status->timespan_name); progress = ((float)_status->current_postprocessing_cycle) / _status->total_postprocessing_cycles; break; case ExportStatus::Tagging: status_text = string_compose (_("Tagging '%3' (timespan %1 of %2)"), _status->timespan, _status->total_timespans, _status->timespan_name); case ExportStatus::Uploading: break; case ExportStatus::Command: status_text = string_compose (_("Running Post-Export Command for '%1'"), _status->timespan_name); break; } _progress_bar.set_text (status_text); if (progress >= 0) { _progress_bar.set_fraction (progress); } else { _progress_bar.set_pulse_step (.1); _progress_bar.pulse (); } return true; }