summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-10-22 15:32:30 +0200
committerCarl Hetherington <cth@carlh.net>2025-10-23 22:39:24 +0200
commitaf517d2d2a0a02ea167ffac4c617845727984720 (patch)
tree496a91e93a8a790e8d3dace58e8961e70b2c9b55
parentbda1a2c382229a5f1a893c015d23524807e022df (diff)
Add DCP-o-matic Processor tool.
-rwxr-xr-xrun/dcpomatic_processor24
-rw-r--r--src/lib/fix_audio_levels_job.cc99
-rw-r--r--src/lib/fix_audio_levels_job.h46
-rw-r--r--src/lib/variant.cc7
-rw-r--r--src/lib/variant.h1
-rw-r--r--src/lib/wscript1
-rw-r--r--src/tools/dcpomatic_processor.cc401
-rw-r--r--src/tools/wscript5
-rw-r--r--src/wx/wx_variant.cc12
-rw-r--r--src/wx/wx_variant.h2
10 files changed, 597 insertions, 1 deletions
diff --git a/run/dcpomatic_processor b/run/dcpomatic_processor
new file mode 100755
index 000000000..0cd37c74a
--- /dev/null
+++ b/run/dcpomatic_processor
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+source $DIR/environment
+binary=$build/src/tools/dcpomatic2_processor
+
+if [[ "$(uname -m)" == arm64 ]]; then
+ env=arm64/11.0
+else
+ env=x86_64/10.10
+fi
+
+export DYLD_LIBRARY_PATH=/Users/cah/osx-environment/$env/lib:/usr/local/lib
+
+if [ "$1" == "--debug" ]; then
+ shift
+ if [[ "$(uname)" == Darwin ]]; then
+ /Applications/Xcode.app/Contents/Developer/usr/bin/lldb $binary $*
+ else
+ gdb --args $binary $*
+ fi
+else
+ $binary $* 2> >(grep -v Gtk-CRITICAL | grep -v Gtk-WARNING)
+fi
diff --git a/src/lib/fix_audio_levels_job.cc b/src/lib/fix_audio_levels_job.cc
new file mode 100644
index 000000000..b5d9e39d9
--- /dev/null
+++ b/src/lib/fix_audio_levels_job.cc
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "analyse_audio_job.h"
+#include "audio_content.h"
+#include "copy_dcp_details_to_film.h"
+#include "dcp_content.h"
+#include "dcp_film_encoder.h"
+#include "dcp_transcode_job.h"
+#include "film.h"
+#include "fix_audio_levels_job.h"
+#include "playlist.h"
+#include <fmt/format.h>
+#include <boost/filesystem.hpp>
+#include <string>
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+using std::string;
+using boost::optional;
+
+
+FixAudioLevelsJob::FixAudioLevelsJob(boost::filesystem::path input_dcp_path, boost::filesystem::path output_dcp_path, float leqm_target, bool make_quieter_dcps_louder)
+ : Job(shared_ptr<const Film>())
+ , _input_dcp_path(input_dcp_path)
+ , _output_dcp_path(output_dcp_path)
+ , _leqm_target(leqm_target)
+ , _make_quieter_dcps_louder(make_quieter_dcps_louder)
+{
+
+}
+
+
+string
+FixAudioLevelsJob::name() const
+{
+ return fmt::format(_("Correcting audio levels of {} to {}"), _input_dcp_path.string(), _leqm_target);
+}
+
+
+string
+FixAudioLevelsJob::json_name() const
+{
+ return N_("fix_audio_levels");
+}
+
+
+void
+FixAudioLevelsJob::run()
+{
+ auto input_dcp = make_shared<DCPContent>(_input_dcp_path);
+ auto film = std::make_shared<Film>(_output_dcp_path);
+ input_dcp->examine(film, shared_from_this(), true);
+ film->add_content({input_dcp});
+ copy_dcp_settings_to_film(input_dcp, film);
+
+ auto playlist = make_shared<Playlist>();
+ playlist->add(film, input_dcp);
+
+ AnalyseAudioJob analyse(film, playlist, false, boost::none);
+ analyse.run();
+
+ auto const level = analyse.analysis().leqm();
+ DCPOMATIC_ASSERT(level);
+
+ if ((!_make_quieter_dcps_louder && *level < _leqm_target) || !input_dcp->audio) {
+ set_progress(1);
+ set_state(FINISHED_OK);
+ }
+
+ input_dcp->audio->set_gain(_leqm_target - *level);
+ auto transcode = make_shared<DCPTranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
+ transcode->set_encoder(make_shared<DCPFilmEncoder>(film, transcode, true));
+ transcode->run();
+
+ set_progress(1);
+ set_state(FINISHED_OK);
+}
diff --git a/src/lib/fix_audio_levels_job.h b/src/lib/fix_audio_levels_job.h
new file mode 100644
index 000000000..eb504bb98
--- /dev/null
+++ b/src/lib/fix_audio_levels_job.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "job.h"
+
+
+class FixAudioLevelsJob : public Job
+{
+public:
+ /** @param input_dcp_path Directory containing the DCP to process.
+ * @param output_dcp_path Directory in which to write the fixed DCP (if required). The DCP will be created as a directory
+ * within output_dcp_path.
+ * @param leqm_target LEQ(m) target.
+ * @param make_quieter_dcps_louder true to increase the gain of DCPs that are less than leqm_target, false to only correct those which are louder.
+ */
+ FixAudioLevelsJob(boost::filesystem::path input_dcp_path, boost::filesystem::path output_dcp_path, float leqm_target, bool make_quieter_dcps_louder);
+
+ std::string name() const override;
+ std::string json_name() const override;
+ void run() override;
+
+private:
+ boost::filesystem::path _input_dcp_path;
+ boost::filesystem::path _output_dcp_path;
+ float _leqm_target;
+ bool _make_quieter_dcps_louder;
+};
+
diff --git a/src/lib/variant.cc b/src/lib/variant.cc
index 81931dcf7..5ca7a9982 100644
--- a/src/lib/variant.cc
+++ b/src/lib/variant.cc
@@ -36,6 +36,7 @@ static char const* _dcpomatic_batch_converter_app = "DCP-o-matic 2 Batch Convert
static char const* _dcpomatic_playlist_editor = "DCP-o-matic Playlist Editor";
static char const* _dcpomatic_combiner = "DCP-o-matic Combiner";
static char const* _dcpomatic_batch_converter = "DCP-o-matic Batch Converter";
+static char const* _dcpomatic_processor = "DCP-o-matic Processor";
static char const* _report_problem_email = "carl@dcpomatic.com";
@@ -107,6 +108,12 @@ variant::dcpomatic_verifier()
}
std::string
+variant::dcpomatic_processor()
+{
+ return _dcpomatic_processor;
+}
+
+std::string
variant::insert_dcpomatic(std::string const& s)
{
return fmt::format(s, _dcpomatic);
diff --git a/src/lib/variant.h b/src/lib/variant.h
index c7e26c1d6..d1fc7ad7e 100644
--- a/src/lib/variant.h
+++ b/src/lib/variant.h
@@ -35,6 +35,7 @@ std::string dcpomatic_kdm_creator();
std::string dcpomatic_player();
std::string dcpomatic_playlist_editor();
std::string dcpomatic_verifier();
+std::string dcpomatic_processor();
std::string insert_dcpomatic(std::string const& s);
std::string insert_dcpomatic_encode_server(std::string const& s);
diff --git a/src/lib/wscript b/src/lib/wscript
index 2e7b0339c..abded0152 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -104,6 +104,7 @@ sources = """
fcpxml.cc
fcpxml_content.cc
fcpxml_decoder.cc
+ fix_audio_levels_job.cc
frame_info.cc
file_group.cc
file_log.cc
diff --git a/src/tools/dcpomatic_processor.cc b/src/tools/dcpomatic_processor.cc
new file mode 100644
index 000000000..0624d3b45
--- /dev/null
+++ b/src/tools/dcpomatic_processor.cc
@@ -0,0 +1,401 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "wx/about_dialog.h"
+#include "wx/dcpomatic_button.h"
+#include "wx/check_box.h"
+#include "wx/editable_list.h"
+#include "wx/i18n_setup.h"
+#include "wx/wx_util.h"
+#include "wx/wx_variant.h"
+#include "lib/cross.h"
+#include "lib/fix_audio_levels_job.h"
+#include "lib/job_manager.h"
+#include "lib/util.h"
+#include "lib/variant.h"
+#include <dcp/search.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/evtloop.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#ifdef __WXGTK__
+#include <X11/Xlib.h>
+#endif
+
+
+using std::exception;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+/** @file src/tools/dcpomatic_processor.cc
+ * @brief A tool to batch-process DCPs in some way (e.g. fix their sound levels)
+ */
+
+
+class DirDialogWrapper : public wxDirDialog
+{
+public:
+ DirDialogWrapper(wxWindow* parent)
+ : wxDirDialog(parent, _("Choose a folder"), {}, wxDD_DIR_MUST_EXIST)
+ {
+
+ }
+
+ vector<boost::filesystem::path> get() const
+ {
+ return dcp::find_potential_dcps(boost::filesystem::path(wx_to_std(GetPath())));
+ }
+
+ void set(boost::filesystem::path)
+ {
+ /* Not used */
+ }
+};
+
+
+
+class DOMFrame : public wxFrame
+{
+public:
+ explicit DOMFrame(wxString const& title)
+ : wxFrame(nullptr, -1, title)
+ /* Use a panel as the only child of the Frame so that we avoid
+ the dark-grey background on Windows.
+ */
+ , _overall_panel(new wxPanel(this, wxID_ANY))
+ {
+ auto bar = new wxMenuBar;
+ setup_menu(bar);
+ SetMenuBar(bar);
+
+ Bind(wxEVT_MENU, boost::bind(&DOMFrame::file_exit, this), wxID_EXIT);
+ Bind(wxEVT_MENU, boost::bind(&DOMFrame::help_about, this), wxID_ABOUT);
+
+#ifdef DCPOMATIC_WINDOWS
+ SetIcon(wxIcon(std_to_wx("id")));
+#endif
+ auto overall_sizer = new wxBoxSizer(wxVERTICAL);
+
+ auto input_dcp_sizer = new wxBoxSizer(wxHORIZONTAL);
+ add_label_to_sizer(input_dcp_sizer, _overall_panel, _("Input DCPs"), true, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto input_dcps = new EditableList<boost::filesystem::path, DirDialogWrapper>(
+ _overall_panel,
+ { EditableListColumn(_("DCP"), 300, true) },
+ boost::bind(&DOMFrame::dcp_paths, this),
+ boost::bind(&DOMFrame::set_dcp_paths, this, _1),
+ [](boost::filesystem::path p, int) { return p.filename().string(); },
+ EditableListTitle::INVISIBLE,
+ EditableListButton::NEW | EditableListButton::REMOVE
+ );
+
+ input_dcp_sizer->Add(input_dcps, 1, wxLEFT | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(input_dcp_sizer, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ auto options_sizer = new wxBoxSizer(wxVERTICAL);
+ _fix_audio_levels = new CheckBox(_overall_panel, _("Fix audio levels"));
+ _fix_audio_levels->set(true);
+ _fix_audio_levels->SetToolTip(
+ _("Tick to change the audio levels to match the given LEQ(m) value")
+ );
+ options_sizer->Add(_fix_audio_levels, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(options_sizer, 0, wxLEFT, DCPOMATIC_DIALOG_BORDER);
+
+ auto actions_sizer = new wxBoxSizer(wxHORIZONTAL);
+ _cancel = new Button(_overall_panel, _("Cancel"));
+ actions_sizer->Add(_cancel, 0, wxRIGHT, DCPOMATIC_SIZER_GAP);
+ _process = new Button(_overall_panel, _("Process"));
+ actions_sizer->Add(_process, 0, wxRIGHT, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(actions_sizer, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER, DCPOMATIC_DIALOG_BORDER);
+
+ _overall_panel->SetSizerAndFit(overall_sizer);
+
+ _cancel->bind(&DOMFrame::cancel_clicked, this);
+ _process->bind(&DOMFrame::process_clicked, this);
+
+ setup_sensitivity();
+ }
+
+private:
+ void file_exit()
+ {
+ Close();
+ }
+
+ void help_about()
+ {
+ AboutDialog dialog(_overall_panel);
+ dialog.ShowModal();
+ }
+
+ void setup_menu(wxMenuBar* m)
+ {
+ auto help = new wxMenu;
+#ifdef DCPOMATIC_OSX
+ /* This just needs to be appended somewhere, it seems - it magically
+ * gets moved to the right place.
+ */
+ help->Append(wxID_EXIT, _("&Exit"));
+ help->Append(wxID_ABOUT, variant::wx::insert_dcpomatic(_("About %s")));
+#else
+ auto file = new wxMenu;
+ file->Append(wxID_EXIT, _("&Quit"));
+ m->Append(file, _("&File"));
+
+ help->Append(wxID_ABOUT, _("About"));
+#endif
+ m->Append(help, _("&Help"));
+ }
+
+ void setup_sensitivity()
+ {
+ auto const work = JobManager::instance()->work_to_do();
+ _cancel->Enable(work);
+ _process->Enable(!_dcp_paths.empty() && !work);
+ }
+
+ void cancel_clicked()
+ {
+ _cancel_pending = true;
+ }
+
+ void process_clicked()
+ {
+ auto job_manager = JobManager::instance();
+ vector<shared_ptr<const FixAudioLevelsJob>> jobs;
+ for (auto const& dcp: _dcp_paths) {
+ auto job = make_shared<FixAudioLevelsJob>(dcp, boost::filesystem::path("/home/carl/tmp/wankz"), 60, true);
+ job_manager->add(job);
+ jobs.push_back(job);
+ }
+
+ setup_sensitivity();
+
+ while (job_manager->work_to_do() && !_cancel_pending) {
+ wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
+ dcpomatic_sleep_milliseconds(250);
+ }
+
+ if (_cancel_pending) {
+ _cancel_pending = false;
+ JobManager::instance()->cancel_all_jobs();
+ setup_sensitivity();
+ return;
+ }
+
+#if 0
+ dcp::VerificationOptions options;
+ options.check_picture_details = _check_picture_details->get();
+ auto job_manager = JobManager::instance();
+ vector<shared_ptr<const VerifyDCPJob>> jobs;
+ for (auto const& dcp: _dcp_paths) {
+ auto job = make_shared<VerifyDCPJob>(
+ std::vector<boost::filesystem::path>{dcp},
+ std::vector<boost::filesystem::path>(),
+ options
+ );
+ job_manager->add(job);
+ jobs.push_back(job);
+ }
+
+ setup_sensitivity();
+
+ while (job_manager->work_to_do() && !_cancel_pending) {
+ wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
+ dcpomatic_sleep_milliseconds(250);
+ auto last = job_manager->last_active_job();
+ if (auto locked = last.lock()) {
+ if (auto dcp = dynamic_pointer_cast<VerifyDCPJob>(locked)) {
+ _progress_panel->update(dcp);
+ }
+ }
+ }
+
+ if (_cancel_pending) {
+ _cancel_pending = false;
+ JobManager::instance()->cancel_all_jobs();
+ _progress_panel->clear();
+ setup_sensitivity();
+ return;
+ }
+
+ DCPOMATIC_ASSERT(_dcp_paths.size() == jobs.size());
+ _result_panel->add(jobs);
+ if (_write_log->get()) {
+ for (size_t i = 0; i < _dcp_paths.size(); ++i) {
+ dcp::TextFormatter formatter(_dcp_paths[i] / "REPORT.txt");
+ dcp::verify_report({ jobs[i]->result() }, formatter);
+ }
+ }
+
+ _progress_panel->clear();
+ setup_sensitivity();
+#endif
+ }
+
+private:
+ void set_dcp_paths (vector<boost::filesystem::path> dcps)
+ {
+ _dcp_paths = dcps;
+ setup_sensitivity();
+ }
+
+ vector<boost::filesystem::path> dcp_paths() const
+ {
+ return _dcp_paths;
+ }
+
+ wxPanel* _overall_panel = nullptr;
+ std::vector<boost::filesystem::path> _dcp_paths;
+ CheckBox* _fix_audio_levels;
+ Button* _cancel;
+ Button* _process;
+ bool _cancel_pending = false;
+};
+
+
+/** @class App
+ * @brief The magic App class for wxWidgets.
+ */
+class App : public wxApp
+{
+public:
+ App()
+ : wxApp()
+ {
+ dcpomatic_setup_path_encoding();
+#ifdef DCPOMATIC_LINUX
+ XInitThreads();
+#endif
+ }
+
+private:
+ bool OnInit() override
+ {
+ try {
+ SetAppName(variant::wx::dcpomatic_processor());
+
+ if (!wxApp::OnInit()) {
+ return false;
+ }
+
+#ifdef DCPOMATIC_LINUX
+ unsetenv("UBUNTU_MENUPROXY");
+#endif
+
+#ifdef DCPOMATIC_OSX
+ dcpomatic_sleep_seconds(1);
+ make_foreground_application();
+#endif
+
+ /* Enable i18n; this will create a Config object
+ to look for a force-configured language. This Config
+ object will be wrong, however, because dcpomatic_setup
+ hasn't yet been called and there aren't any filters etc.
+ set up yet.
+ */
+ dcpomatic::wx::setup_i18n();
+
+ /* Set things up, including filters etc.
+ which will now be internationalised correctly.
+ */
+ dcpomatic_setup();
+
+ /* Force the configuration to be re-loaded correctly next
+ time it is needed.
+ */
+ Config::drop();
+
+ _frame = new DOMFrame(variant::wx::dcpomatic_processor());
+ SetTopWindow(_frame);
+ _frame->Maximize();
+ _frame->Show();
+ }
+ catch (exception& e)
+ {
+ error_dialog(nullptr, variant::wx::insert_dcpomatic_processor(char_to_wx("%s could not start.")), std_to_wx(e.what()));
+ }
+
+ return true;
+ }
+
+ void report_exception()
+ {
+ try {
+ throw;
+ } catch (FileError& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.file().string().c_str()),
+ dcpomatic::wx::report_problem()
+ )
+ );
+ } catch (boost::filesystem::filesystem_error& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s (%s) (%s)\n\n%s"),
+ std_to_wx(e.what()),
+ std_to_wx(e.path1().string()),
+ std_to_wx(e.path2().string()),
+ dcpomatic::wx::report_problem()
+ )
+ );
+ } catch (exception& e) {
+ error_dialog(
+ nullptr,
+ wxString::Format(
+ _("An exception occurred: %s.\n\n%s"),
+ std_to_wx(e.what()),
+ dcpomatic::wx::report_problem()
+ )
+ );
+ } catch (...) {
+ error_dialog(nullptr, wxString::Format(_("An unknown exception occurred. %s"), dcpomatic::wx::report_problem()));
+ }
+ }
+
+ /* An unhandled exception has occurred inside the main event loop */
+ bool OnExceptionInMainLoop() override
+ {
+ report_exception();
+ return false;
+ }
+
+ void OnUnhandledException() override
+ {
+ report_exception();
+ }
+
+ DOMFrame* _frame = nullptr;
+};
+
+
+IMPLEMENT_APP(App)
diff --git a/src/tools/wscript b/src/tools/wscript
index 3128486f5..a577214f9 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -34,6 +34,7 @@ def description(tool, variant):
'dcpomatic_playlist': 'DCP-o-matic Playlist Editor',
'dcpomatic_combiner': 'DCP-o-matic Combiner',
'dcpomatic_verifier': 'DCP-o-matic Verifier',
+ 'dcpomatic_processor': 'DCP-o-matic Processor',
}
return descriptions[tool] if tool in descriptions else tool
@@ -143,7 +144,8 @@ def build(bld):
'dcpomatic_playlist',
'dcpomatic_combiner',
'dcpomatic_editor',
- 'dcpomatic_verifier']
+ 'dcpomatic_verifier',
+ 'dcpomatic_processor']
if bld.env.ENABLE_DISK:
gui_tools.append('dcpomatic_disk')
@@ -181,6 +183,7 @@ def pot(bld):
dcpomatic_playlist.cc
dcpomatic_server.cc
dcpomatic_verifier.cc
+ dcpomatic_processor.cc
"""
i18n.pot(os.path.join('src', 'tools'), cc, 'dcpomatic')
diff --git a/src/wx/wx_variant.cc b/src/wx/wx_variant.cc
index 96b00bba4..dd2021209 100644
--- a/src/wx/wx_variant.cc
+++ b/src/wx/wx_variant.cc
@@ -86,6 +86,12 @@ variant::wx::dcpomatic_verifier()
}
wxString
+variant::wx::dcpomatic_processor()
+{
+ return std_to_wx(variant::dcpomatic_processor());
+}
+
+wxString
variant::wx::insert_dcpomatic(wxString const& s)
{
return wxString::Format(s, dcpomatic());
@@ -140,6 +146,12 @@ variant::wx::insert_dcpomatic_verifier(wxString const& s)
}
wxString
+variant::wx::insert_dcpomatic_processor(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_processor());
+}
+
+wxString
variant::wx::report_problem_email()
{
return std_to_wx(variant::report_problem_email());
diff --git a/src/wx/wx_variant.h b/src/wx/wx_variant.h
index 69565f9e9..8d71c0b58 100644
--- a/src/wx/wx_variant.h
+++ b/src/wx/wx_variant.h
@@ -39,6 +39,7 @@ wxString dcpomatic_kdm_creator();
wxString dcpomatic_player();
wxString dcpomatic_playlist_editor();
wxString dcpomatic_verifier();
+wxString dcpomatic_processor();
wxString insert_dcpomatic(wxString const& s);
wxString insert_dcpomatic_batch_converter(wxString const& s);
@@ -49,6 +50,7 @@ wxString insert_dcpomatic_kdm_creator(wxString const& s);
wxString insert_dcpomatic_player(wxString const& s);
wxString insert_dcpomatic_playlist_editor(wxString const& s);
wxString insert_dcpomatic_verifier(wxString const& s);
+wxString insert_dcpomatic_processor(wxString const& s);
wxString report_problem_email();