summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2020-09-11 01:07:46 +0200
committerCarl Hetherington <cth@carlh.net>2020-09-20 01:30:41 +0200
commit5a3e836da9480bca0c3ef3384fa2010f358ccc7e (patch)
tree2a15662a32dc00c86740641db6eedaf99daacb42 /src
parent44dde2ee811eb35535633d760e6c0671cdf45cae (diff)
Add dcpomatic_combine tool (#1245).
Diffstat (limited to 'src')
-rw-r--r--src/lib/combine_dcp_job.cc74
-rw-r--r--src/lib/combine_dcp_job.h39
-rw-r--r--src/lib/wscript1
-rw-r--r--src/tools/dcpomatic_combiner.cc313
-rw-r--r--src/tools/wscript4
5 files changed, 429 insertions, 2 deletions
diff --git a/src/lib/combine_dcp_job.cc b/src/lib/combine_dcp_job.cc
new file mode 100644
index 000000000..b3c29b205
--- /dev/null
+++ b/src/lib/combine_dcp_job.cc
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 2020 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 "combine_dcp_job.h"
+#include <dcp/combine.h>
+#include <dcp/exceptions.h>
+
+#include "i18n.h"
+
+
+using std::string;
+using std::vector;
+using boost::shared_ptr;
+
+
+CombineDCPJob::CombineDCPJob (vector<boost::filesystem::path> inputs, boost::filesystem::path output)
+ : Job (shared_ptr<Film>())
+ , _inputs (inputs)
+ , _output (output)
+{
+
+}
+
+
+string
+CombineDCPJob::name () const
+{
+ return _("Combine DCPs");
+}
+
+
+string
+CombineDCPJob::json_name () const
+{
+ return N_("combine_dcps");
+}
+
+
+void
+CombineDCPJob::run ()
+{
+ try {
+ dcp::combine (_inputs, _output);
+ } catch (dcp::CombineError& e) {
+ set_state (FINISHED_ERROR);
+ set_error (e.what(), "");
+ return;
+ } catch (dcp::ReadError& e) {
+ set_state (FINISHED_ERROR);
+ set_error (e.what(), e.detail().get_value_or(""));
+ return;
+ }
+
+ set_progress (1);
+ set_state (FINISHED_OK);
+}
diff --git a/src/lib/combine_dcp_job.h b/src/lib/combine_dcp_job.h
new file mode 100644
index 000000000..97bf20110
--- /dev/null
+++ b/src/lib/combine_dcp_job.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2020 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"
+#include <boost/filesystem.hpp>
+
+
+class CombineDCPJob : public Job
+{
+public:
+ CombineDCPJob (std::vector<boost::filesystem::path> inputs, boost::filesystem::path output);
+
+ std::string name () const;
+ std::string json_name () const;
+ void run ();
+
+private:
+ std::vector<boost::filesystem::path> _inputs;
+ boost::filesystem::path _output;
+};
+
diff --git a/src/lib/wscript b/src/lib/wscript
index 0c9cddfa4..04044a8c3 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -55,6 +55,7 @@ sources = """
config.cc
content.cc
content_factory.cc
+ combine_dcp_job.cc
copy_dcp_details_to_film.cc
create_cli.cc
cross_common.cc
diff --git a/src/tools/dcpomatic_combiner.cc b/src/tools/dcpomatic_combiner.cc
new file mode 100644
index 000000000..5f1a7722a
--- /dev/null
+++ b/src/tools/dcpomatic_combiner.cc
@@ -0,0 +1,313 @@
+/*
+ Copyright (C) 2020 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/dir_picker_ctrl.h"
+#include "wx/editable_list.h"
+#include "wx/wx_signal_manager.h"
+#include "lib/combine_dcp_job.h"
+#include "lib/config.h"
+#include "lib/cross.h"
+#include "lib/job_manager.h"
+#include "lib/util.h"
+#include <dcp/combine.h>
+DCPOMATIC_DISABLE_WARNINGS
+#include <wx/filepicker.h>
+DCPOMATIC_ENABLE_WARNINGS
+#include <wx/wx.h>
+#include <boost/bind.hpp>
+#include <boost/filesystem.hpp>
+#include <exception>
+
+
+using std::exception;
+using std::string;
+using std::vector;
+using boost::dynamic_pointer_cast;
+using boost::optional;
+using boost::shared_ptr;
+
+
+static string
+display_string (boost::filesystem::path p, int)
+{
+ return p.filename().string();
+}
+
+
+class DirDialogWrapper : public wxDirDialog
+{
+public:
+ DirDialogWrapper (wxWindow* parent)
+ : wxDirDialog (parent, _("Choose a DCP folder"), wxT(""), wxDD_DIR_MUST_EXIST)
+ {
+
+ }
+
+ boost::filesystem::path get () const
+ {
+ return 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 (0, -1, title)
+ {
+ /* Use a panel as the only child of the Frame so that we avoid
+ the dark-grey background on Windows.
+ */
+ wxPanel* overall_panel = new wxPanel (this);
+ wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+ s->Add (overall_panel, 1, wxEXPAND);
+ SetSizer (s);
+
+ vector<EditableListColumn> columns;
+ columns.push_back(EditableListColumn(_("Input DCP"), 600, true));
+
+ _input = new EditableList<boost::filesystem::path, DirDialogWrapper>(
+ overall_panel,
+ columns,
+ boost::bind(&DOMFrame::inputs, this),
+ boost::bind(&DOMFrame::set_inputs, this, _1),
+ &display_string,
+ false,
+ true
+ );
+
+ wxBoxSizer* output = new wxBoxSizer (wxHORIZONTAL);
+ add_label_to_sizer (output, overall_panel, _("Output DCP folder"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _output = new DirPickerCtrl (overall_panel);
+ output->Add (_output, 1, wxEXPAND);
+
+ _combine = new Button (overall_panel, _("Combine"));
+
+ wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+ sizer->Add (_input, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
+ sizer->Add (output, 0, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
+ sizer->Add (_combine, 0, wxALL | wxALIGN_RIGHT, DCPOMATIC_DIALOG_BORDER);
+ overall_panel->SetSizer (sizer);
+ Fit ();
+ SetSize (768, GetSize().GetHeight() + 32);
+
+ _combine->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::combine, this));
+ _output->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind(&DOMFrame::setup_sensitivity, this));
+
+ setup_sensitivity ();
+ }
+
+private:
+ void set_inputs (vector<boost::filesystem::path> inputs)
+ {
+ _inputs = inputs;
+ }
+
+ vector<boost::filesystem::path> inputs () const
+ {
+ return _inputs;
+ }
+
+ void combine ()
+ {
+ boost::filesystem::path const output = wx_to_std(_output->GetPath());
+
+ if (boost::filesystem::is_directory(output) && !boost::filesystem::is_empty(output)) {
+ if (!confirm_dialog (
+ this,
+ std_to_wx (
+ String::compose(wx_to_std(_("The directory %1 already exists and is not empty. "
+ "Are you sure you want to use it?")),
+ output.string())
+ )
+ )) {
+ return;
+ }
+ } else if (boost::filesystem::is_regular_file(output)) {
+ error_dialog (
+ this,
+ String::compose (wx_to_std(_("%1 already exists as a file, so you cannot use it for a DCP.")), output.string())
+ );
+ return;
+ }
+
+ JobManager* jm = JobManager::instance ();
+ jm->add (shared_ptr<Job>(new CombineDCPJob(_inputs, output)));
+ bool const ok = display_progress (_("DCP-o-matic Combine"), _("Combining DCPs"));
+ if (!ok) {
+ return;
+ }
+
+ DCPOMATIC_ASSERT (!jm->get().empty());
+ shared_ptr<CombineDCPJob> last = dynamic_pointer_cast<CombineDCPJob> (jm->get().back());
+ DCPOMATIC_ASSERT (last);
+
+ if (last->finished_ok()) {
+ message_dialog (this, _("DCPs combined successfully."));
+ } else {
+ wxString m = std_to_wx(last->error_summary());
+ if (!last->error_details().empty()) {
+ m += wxString::Format(" (%s)", std_to_wx(last->error_details()));
+ }
+ error_dialog (this, m);
+ }
+ }
+
+ void setup_sensitivity ()
+ {
+ _combine->Enable (!_output->GetPath().IsEmpty());
+ }
+
+ EditableList<boost::filesystem::path, DirDialogWrapper>* _input;
+ DirPickerCtrl* _output;
+ vector<boost::filesystem::path> _inputs;
+ Button* _combine;
+};
+
+
+class App : public wxApp
+{
+public:
+ App ()
+ : _frame (0)
+ {}
+
+ bool OnInit ()
+ {
+ try {
+ Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
+ Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
+
+ SetAppName (_("DCP-o-matic Combiner"));
+
+ if (!wxApp::OnInit()) {
+ return false;
+ }
+
+#ifdef DCPOMATIC_LINUX
+ unsetenv ("UBUNTU_MENUPROXY");
+#endif
+
+#ifdef DCPOMATIC_OSX
+ make_foreground_application ();
+#endif
+
+ dcpomatic_setup_path_encoding ();
+
+ /* 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_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 (_("DCP-o-matic DCP Combiner"));
+ SetTopWindow (_frame);
+
+ _frame->Show ();
+
+ signal_manager = new wxSignalManager (this);
+ Bind (wxEVT_IDLE, boost::bind(&App::idle, this, _1));
+ }
+ catch (exception& e)
+ {
+ error_dialog (0, wxString::Format ("DCP-o-matic DCP Combiner could not start."), std_to_wx(e.what()));
+ return false;
+ }
+
+ return true;
+ }
+
+ void config_failed_to_load ()
+ {
+ message_dialog (_frame, _("The existing configuration failed to load. Default values will be used instead. These may take a short time to create."));
+ }
+
+ void config_warning (string m)
+ {
+ message_dialog (_frame, std_to_wx(m));
+ }
+
+ void idle (wxIdleEvent& ev)
+ {
+ signal_manager->ui_idle ();
+ ev.Skip ();
+ }
+
+ void report_exception ()
+ {
+ try {
+ throw;
+ } catch (FileError& e) {
+ error_dialog (
+ 0,
+ wxString::Format (
+ _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
+ std_to_wx (e.what()),
+ std_to_wx (e.file().string().c_str ())
+ )
+ );
+ } catch (exception& e) {
+ error_dialog (
+ 0,
+ wxString::Format (
+ _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
+ std_to_wx (e.what ())
+ )
+ );
+ } catch (...) {
+ error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
+ }
+ }
+
+ bool OnExceptionInMainLoop ()
+ {
+ report_exception ();
+ /* This will terminate the program */
+ return false;
+ }
+
+ void OnUnhandledException ()
+ {
+ report_exception ();
+ }
+
+ DOMFrame* _frame;
+};
+
+IMPLEMENT_APP (App)
diff --git a/src/tools/wscript b/src/tools/wscript
index 7eeeecddf..9caa84e9b 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+# Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
#
# This file is part of DCP-o-matic.
#
@@ -73,7 +73,7 @@ def build(bld):
elif bld.env.VARIANT == 'swaroop-studio':
gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'swaroop_dcpomatic_playlist']
else:
- gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist']
+ gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist', 'dcpomatic_combiner']
if bld.env.ENABLE_DISK:
gui_tools.append('dcpomatic_disk')