Basics of send-to-batch-converter; not tested on Windows nor OS X.
[dcpomatic.git] / src / tools / dcpomatic.cc
index 0a105fa6bc66a24b60b054c0682702166c73cbe8..16c7a08e5e1ef5100c39df55162f6a8fdce43fc1 100644 (file)
  *  @brief The main DCP-o-matic GUI.
  */
 
-#include "lib/film.h"
-#include "lib/config.h"
-#include "lib/util.h"
-#include "lib/version.h"
-#include "lib/signal_manager.h"
-#include "lib/log.h"
-#include "lib/job_manager.h"
-#include "lib/transcode_job.h"
-#include "lib/exceptions.h"
-#include "lib/cinema.h"
-#include "lib/kdm.h"
-#include "lib/send_kdm_email_job.h"
-#include "lib/server_finder.h"
-#include "lib/update.h"
-#include "lib/content_factory.h"
 #include "wx/film_viewer.h"
 #include "wx/film_editor.h"
 #include "wx/job_manager_view.h"
 #include "wx/config_dialog.h"
 #include "wx/wx_util.h"
 #include "wx/new_film_dialog.h"
-#include "wx/properties_dialog.h"
 #include "wx/wx_signal_manager.h"
 #include "wx/about_dialog.h"
 #include "wx/kdm_dialog.h"
+#include "wx/self_dkdm_dialog.h"
 #include "wx/servers_list_dialog.h"
 #include "wx/hints_dialog.h"
 #include "wx/update_dialog.h"
 #include "wx/content_panel.h"
 #include "wx/report_problem_dialog.h"
+#include "wx/video_waveform_dialog.h"
+#include "lib/film.h"
+#include "lib/config.h"
+#include "lib/util.h"
+#include "lib/video_content.h"
+#include "lib/version.h"
+#include "lib/signal_manager.h"
+#include "lib/log.h"
+#include "lib/job_manager.h"
+#include "lib/exceptions.h"
+#include "lib/cinema.h"
+#include "lib/screen_kdm.h"
+#include "lib/send_kdm_email_job.h"
+#include "lib/encode_server_finder.h"
+#include "lib/update_checker.h"
+#include "lib/cross.h"
+#include "lib/content_factory.h"
+#include "lib/compose.hpp"
+#include "lib/cinema_kdms.h"
+#include "lib/dcpomatic_socket.h"
 #include <dcp/exceptions.h>
+#include <dcp/raw_convert.h>
 #include <wx/generic/aboutdlgg.h>
 #include <wx/stdpaths.h>
 #include <wx/cmdline.h>
 #include <wx/preferences.h>
+#include <wx/splash.h>
 #ifdef __WXMSW__
 #include <shellapi.h>
 #endif
@@ -83,6 +90,7 @@ using std::list;
 using std::exception;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
+using dcp::raw_convert;
 
 class FilmChangedDialog
 {
@@ -92,9 +100,8 @@ public:
                _dialog = new wxMessageDialog (
                        0,
                        wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (name).data()),
-                       /* TRANSLATORS: this is the heading for a dialog box, which tells the user that the current
-                          project (Film) has been changed since it was last saved.
-                       */
+                       /// TRANSLATORS: this is the heading for a dialog box, which tells the user that the current
+                       /// project (Film) has been changed since it was last saved.
                        _("Film changed"),
                        wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
                        );
@@ -129,15 +136,17 @@ enum {
        ID_file_new = 1,
        ID_file_open,
        ID_file_save,
-       ID_file_properties,
        ID_file_history,
        /* Allow spare IDs after _history for the recent files list */
        ID_content_scale_to_fit_width = 100,
        ID_content_scale_to_fit_height,
        ID_jobs_make_dcp,
+       ID_jobs_make_dcp_batch,
        ID_jobs_make_kdms,
+       ID_jobs_make_self_dkdm,
        ID_jobs_send_dcp_to_tms,
        ID_jobs_show_dcp,
+       ID_tools_video_waveform,
        ID_tools_hints,
        ID_tools_encoding_servers,
        ID_tools_check_for_updates,
@@ -152,6 +161,7 @@ class DOMFrame : public wxFrame
 public:
        DOMFrame (wxString const & title)
                : wxFrame (NULL, -1, title)
+               , _video_waveform_dialog (0)
                , _hints_dialog (0)
                , _servers_list_dialog (0)
                , _config_dialog (0)
@@ -159,6 +169,7 @@ public:
                , _history_items (0)
                , _history_position (0)
                , _history_separator (0)
+               , _update_news_requested (false)
        {
 #if defined(DCPOMATIC_WINDOWS)
                if (Config::instance()->win32_console ()) {
@@ -190,7 +201,6 @@ public:
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_new, this),                ID_file_new);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_open, this),               ID_file_open);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_save, this),               ID_file_save);
-               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_properties, this),         ID_file_properties);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_history, this, _1),        ID_file_history, ID_file_history + HISTORY_SIZE);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_exit, this),               wxID_EXIT);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::edit_preferences, this),        wxID_PREFERENCES);
@@ -198,8 +208,11 @@ public:
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::content_scale_to_fit_height, this), ID_content_scale_to_fit_height);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_make_dcp, this),           ID_jobs_make_dcp);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_make_kdms, this),          ID_jobs_make_kdms);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_make_dcp_batch, this),     ID_jobs_make_dcp_batch);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_make_self_dkdm, this),     ID_jobs_make_self_dkdm);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_send_dcp_to_tms, this),    ID_jobs_send_dcp_to_tms);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::jobs_show_dcp, this),           ID_jobs_show_dcp);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::tools_video_waveform, this),    ID_tools_video_waveform);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::tools_hints, this),             ID_tools_hints);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::tools_encoding_servers, this),  ID_tools_encoding_servers);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::tools_check_for_updates, this), ID_tools_check_for_updates);
@@ -243,6 +256,8 @@ public:
 
                /* Instantly save any config changes when using the DCP-o-matic GUI */
                Config::instance()->Changed.connect (boost::bind (&Config::write, Config::instance ()));
+
+               UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
        }
 
        void new_film (boost::filesystem::path path)
@@ -370,13 +385,6 @@ private:
                _film->write_metadata ();
        }
 
-       void file_properties ()
-       {
-               PropertiesDialog* d = new PropertiesDialog (this, _film);
-               d->ShowModal ();
-               d->Destroy ();
-       }
-
        void file_history (wxCommandEvent& event)
        {
                vector<boost::filesystem::path> history = Config::instance()->history ();
@@ -449,11 +457,23 @@ private:
                }
 
                try {
+                       list<ScreenKDM> screen_kdms = _film->make_kdms (d->screens(), d->cpl(), d->from(), d->until(), d->formulation());
                        if (d->write_to ()) {
-                               write_kdm_files (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation (), d->directory ());
+                               ScreenKDM::write_files (
+                                       _film->name(),
+                                       screen_kdms,
+                                       d->directory()
+                                       );
                        } else {
                                JobManager::instance()->add (
-                                       shared_ptr<Job> (new SendKDMEmailJob (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
+                                       shared_ptr<Job> (new SendKDMEmailJob (
+                                                                _film->name(),
+                                                                _film->dcp_name(),
+                                                                d->from(),
+                                                                d->until(),
+                                                                CinemaKDMs::collect (screen_kdms),
+                                                                _film->log()
+                                                                ))
                                        );
                        }
                } catch (dcp::NotEncryptedError& e) {
@@ -467,6 +487,79 @@ private:
                d->Destroy ();
        }
 
+       void jobs_make_dcp_batch ()
+       {
+               if (!_film) {
+                       return;
+               }
+
+               _film->write_metadata ();
+
+               /* i = 0; try to connect via socket
+                  i = 1; try again, and then try to start the batch converter
+                  i = 2; try again.
+                  i = 3; try again.
+               */
+               for (int i = 0; i < 4; ++i) {
+                       try {
+                               boost::asio::io_service io_service;
+                               boost::asio::ip::tcp::resolver resolver (io_service);
+                               boost::asio::ip::tcp::resolver::query query ("localhost", raw_convert<string> (Config::instance()->server_port_base() + 2));
+                               boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
+                               Socket socket (1);
+                               socket.connect (*endpoint_iterator);
+                               string s = _film->directory().string ();
+                               socket.write (s.length() + 1);
+                               socket.write ((uint8_t *) s.c_str(), s.length() + 1);
+                               return;
+                       } catch (...) {
+                       }
+
+                       if (i == 1) {
+                               start_batch_converter (wx_to_std (wxStandardPaths::Get().GetExecutablePath()));
+                       }
+
+                       dcpomatic_sleep (1);
+               }
+
+               error_dialog (this, _("Could not find batch converter."));
+       }
+
+       void jobs_make_self_dkdm ()
+       {
+               if (!_film) {
+                       return;
+               }
+
+               SelfDKDMDialog* d = new SelfDKDMDialog (this, _film);
+               if (d->ShowModal () != wxID_OK) {
+                       d->Destroy ();
+                       return;
+               }
+
+               try {
+                       dcp::EncryptedKDM kdm = _film->make_kdm (
+                               Config::instance()->decryption_chain()->leaf(),
+                               vector<dcp::Certificate> (),
+                               d->cpl (),
+                               dcp::LocalTime ("2012-01-01T01:00:00+00:00"),
+                               dcp::LocalTime ("2112-01-01T01:00:00+00:00"),
+                               dcp::MODIFIED_TRANSITIONAL_1
+                               );
+
+                       string const name = tidy_for_filename(_film->name()) + "_DKDM.kdm.xml";
+                       kdm.as_xml (d->directory() / name);
+               } catch (dcp::NotEncryptedError& e) {
+                       error_dialog (this, _("CPL's content is not encrypted."));
+               } catch (exception& e) {
+                       error_dialog (this, e.what ());
+               } catch (...) {
+                       error_dialog (this, _("An unknown exception occurred."));
+               }
+
+               d->Destroy ();
+       }
+
        void content_scale_to_fit_width ()
        {
                VideoContentList vc = _film_editor->content_panel()->selected_video ();
@@ -522,6 +615,15 @@ private:
 #endif
        }
 
+       void tools_video_waveform ()
+       {
+               if (!_video_waveform_dialog) {
+                       _video_waveform_dialog = new VideoWaveformDialog (this, _film_viewer);
+               }
+
+               _video_waveform_dialog->Show ();
+       }
+
        void tools_hints ()
        {
                if (!_hints_dialog) {
@@ -543,6 +645,7 @@ private:
        void tools_check_for_updates ()
        {
                UpdateChecker::instance()->run ();
+               _update_news_requested = true;
        }
 
        void help_about ()
@@ -600,7 +703,7 @@ private:
        {
                list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
                list<shared_ptr<Job> >::iterator i = jobs.begin();
-               while (i != jobs.end() && dynamic_pointer_cast<TranscodeJob> (*i) == 0) {
+               while (i != jobs.end() && (*i)->json_name() != "transcode") {
                        ++i;
                }
                bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
@@ -664,8 +767,6 @@ private:
                add_item (_file_menu, _("&Open...\tCtrl-O"), ID_file_open, ALWAYS);
                _file_menu->AppendSeparator ();
                add_item (_file_menu, _("&Save\tCtrl-S"), ID_file_save, NEEDS_FILM);
-               _file_menu->AppendSeparator ();
-               add_item (_file_menu, _("&Properties..."), ID_file_properties, NEEDS_FILM);
 
                _history_position = _file_menu->GetMenuItems().GetCount();
 
@@ -692,11 +793,14 @@ private:
 
                wxMenu* jobs_menu = new wxMenu;
                add_item (jobs_menu, _("&Make DCP\tCtrl-M"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+               add_item (jobs_menu, _("Make DCP in &batch converter\tCtrl-B"), ID_jobs_make_dcp_batch, NEEDS_FILM | NOT_DURING_DCP_CREATION);
                add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
+               add_item (jobs_menu, _("Make DKDM for DCP-o-matic..."), ID_jobs_make_self_dkdm, NEEDS_FILM);
                add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
                add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
 
                wxMenu* tools = new wxMenu;
+               add_item (tools, _("Video waveform..."), ID_tools_video_waveform, NEEDS_FILM);
                add_item (tools, _("Hints..."), ID_tools_hints, 0);
                add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
                add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
@@ -709,7 +813,7 @@ private:
 #else
                add_item (help, _("About"), wxID_ABOUT, ALWAYS);
 #endif
-               add_item (help, _("Report a problem..."), ID_help_report_a_problem, ALWAYS);
+               add_item (help, _("Report a problem..."), ID_help_report_a_problem, NEEDS_FILM);
 
                m->Append (_file_menu, _("&File"));
 #ifndef __WXOSX__
@@ -753,8 +857,37 @@ private:
                _history_items = history.size ();
        }
 
+       void update_checker_state_changed ()
+       {
+               UpdateChecker* uc = UpdateChecker::instance ();
+
+               bool const announce =
+                       _update_news_requested ||
+                       (uc->stable() && Config::instance()->check_for_updates()) ||
+                       (uc->test() && Config::instance()->check_for_updates() && Config::instance()->check_for_test_updates());
+
+               _update_news_requested = false;
+
+               if (!announce) {
+                       return;
+               }
+
+               if (uc->state() == UpdateChecker::YES) {
+                       UpdateDialog* dialog = new UpdateDialog (this, uc->stable (), uc->test ());
+                       dialog->ShowModal ();
+                       dialog->Destroy ();
+               } else if (uc->state() == UpdateChecker::FAILED) {
+                       error_dialog (this, _("The DCP-o-matic download server could not be contacted."));
+               } else {
+                       error_dialog (this, _("There are no new versions of DCP-o-matic available."));
+               }
+
+               _update_news_requested = false;
+       }
+
        FilmEditor* _film_editor;
        FilmViewer* _film_viewer;
+       VideoWaveformDialog* _video_waveform_dialog;
        HintsDialog* _hints_dialog;
        ServersListDialog* _servers_list_dialog;
        wxPreferencesEditor* _config_dialog;
@@ -764,6 +897,7 @@ private:
        int _history_position;
        wxMenuItem* _history_separator;
        boost::signals2::scoped_connection _config_changed_connection;
+       bool _update_news_requested;
 };
 
 static const wxCmdLineEntryDesc command_line_description[] = {
@@ -789,6 +923,22 @@ private:
        bool OnInit ()
        try
        {
+               wxInitAllImageHandlers ();
+
+               wxSplashScreen* splash = 0;
+               try {
+                       if (!Config::have_existing ()) {
+                               wxBitmap bitmap;
+                               boost::filesystem::path p = shared_path () / "splash.png";
+                               if (bitmap.LoadFile (std_to_wx (p.string ()), wxBITMAP_TYPE_PNG)) {
+                                       splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1);
+                                       wxYield ();
+                               }
+                       }
+               } catch (boost::filesystem::filesystem_error& e) {
+                       /* Maybe we couldn't find the splash image; never mind */
+               }
+
                SetAppName (_("DCP-o-matic"));
 
                if (!wxApp::OnInit()) {
@@ -805,7 +955,7 @@ private:
                TransformProcessType (&serial, kProcessTransformToForegroundApplication);
 #endif
 
-               wxInitAllImageHandlers ();
+               dcpomatic_setup_path_encoding ();
 
                /* Enable i18n; this will create a Config object
                   to look for a force-configured language.  This Config
@@ -828,6 +978,9 @@ private:
                _frame = new DOMFrame (_("DCP-o-matic"));
                SetTopWindow (_frame);
                _frame->Maximize ();
+               if (splash) {
+                       splash->Destroy ();
+               }
                _frame->Show ();
 
                if (!_film_to_load.empty() && boost::filesystem::is_directory (_film_to_load)) {
@@ -852,7 +1005,6 @@ private:
                _timer.reset (new wxTimer (this));
                _timer->Start (1000);
 
-               UpdateChecker::instance()->StateChanged.connect (boost::bind (&App::update_checker_state_changed, this));
                if (Config::instance()->check_for_updates ()) {
                        UpdateChecker::instance()->run ();
                }
@@ -889,26 +1041,43 @@ private:
                return true;
        }
 
-       /* An unhandled exception has occurred inside the main event loop */
-       bool OnExceptionInMainLoop ()
+       void report_exception ()
        {
                try {
                        throw;
                } catch (FileError& e) {
-                       error_dialog (0, wxString::Format (_("An exception occurred: %s in %s.\n\n" + REPORT_PROBLEM), e.what(), e.file().string().c_str ()));
+                       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"), e.what ()) + "  " + REPORT_PROBLEM);
+                       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);
                }
+       }
 
+       /* An unhandled exception has occurred inside the main event loop */
+       bool OnExceptionInMainLoop ()
+       {
+               report_exception ();
                /* This will terminate the program */
                return false;
        }
 
        void OnUnhandledException ()
        {
-               error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
+               report_exception ();
        }
 
        void idle ()
@@ -919,30 +1088,12 @@ private:
        void check ()
        {
                try {
-                       ServerFinder::instance()->rethrow ();
+                       EncodeServerFinder::instance()->rethrow ();
                } catch (exception& e) {
                        error_dialog (0, std_to_wx (e.what ()));
                }
        }
 
-       void update_checker_state_changed ()
-       {
-               UpdateChecker* uc = UpdateChecker::instance ();
-               if (uc->state() == UpdateChecker::YES && (uc->stable() || uc->test())) {
-                       UpdateDialog* dialog = new UpdateDialog (_frame, uc->stable (), uc->test ());
-                       dialog->ShowModal ();
-                       dialog->Destroy ();
-               } else if (uc->state() == UpdateChecker::FAILED) {
-                       if (!UpdateChecker::instance()->last_emit_was_first ()) {
-                               error_dialog (_frame, _("The DCP-o-matic download server could not be contacted."));
-                       }
-               } else {
-                       if (!UpdateChecker::instance()->last_emit_was_first ()) {
-                               error_dialog (_frame, _("There are no new versions of DCP-o-matic available."));
-                       }
-               }
-       }
-
        DOMFrame* _frame;
        shared_ptr<wxTimer> _timer;
        string _film_to_load;