More automated renaming.
[dcpomatic.git] / src / tools / dcpomatic_player.cc
index 89bff5caef2d0edb8b35c083afce03bc1eb85c5c..44c6b8606cbe57c9364bfcde8fae496dcdccfb55 100644 (file)
 #include "lib/job_manager.h"
 #include "lib/job.h"
 #include "lib/video_content.h"
-#include "lib/subtitle_content.h"
+#include "lib/text_content.h"
 #include "lib/ratio.h"
 #include "lib/verify_dcp_job.h"
+#include "lib/dcp_examiner.h"
+#include "lib/examine_content_job.h"
+#include "lib/server.h"
+#include "lib/dcpomatic_socket.h"
 #include "wx/wx_signal_manager.h"
 #include "wx/wx_util.h"
 #include "wx/about_dialog.h"
 #undef check
 #endif
 
+#define MAX_CPLS 32
+
 using std::string;
 using std::cout;
+using std::list;
 using std::exception;
 using std::vector;
 using boost::shared_ptr;
+using boost::scoped_array;
 using boost::optional;
 using boost::dynamic_pointer_cast;
+using boost::thread;
+using boost::bind;
 
 enum {
        ID_file_open = 1,
@@ -70,6 +80,9 @@ enum {
        ID_file_history,
        /* Allow spare IDs after _history for the recent files list */
        ID_file_close = 100,
+       ID_view_cpl,
+       /* Allow spare IDs for CPLs */
+       ID_view_closed_captions = 200,
        ID_view_scale_appropriate,
        ID_view_scale_full,
        ID_view_scale_half,
@@ -77,6 +90,10 @@ enum {
        ID_help_report_a_problem,
        ID_tools_verify,
        ID_tools_check_for_updates,
+       /* IDs for shortcuts (with no associated menu item) */
+       ID_start_stop,
+       ID_back_frame,
+       ID_forward_frame
 };
 
 class DOMFrame : public wxFrame
@@ -118,7 +135,8 @@ public:
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_close, this), ID_file_close);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_preferences, this), wxID_PREFERENCES);
-               Bind (wxEVT_MENU, boost::bind (&DOMFrame::set_decode_reduction, this, optional<int>()), ID_view_scale_appropriate);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_closed_captions, this), ID_view_closed_captions);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_cpl, this, _1), ID_view_cpl, ID_view_cpl + MAX_CPLS);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::set_decode_reduction, this, optional<int>(0)), ID_view_scale_full);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::set_decode_reduction, this, optional<int>(1)), ID_view_scale_half);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::set_decode_reduction, this, optional<int>(2)), ID_view_scale_quarter);
@@ -141,12 +159,25 @@ public:
                overall_panel->SetSizer (main_sizer);
 
 #ifdef __WXOSX__
-               wxAcceleratorEntry* accel = new wxAcceleratorEntry[1];
-               accel[0].Set(wxACCEL_CTRL, static_cast<int>('W'), ID_file_close);
-               wxAcceleratorTable accel_table (1, accel);
+               int accelerators = 4;
+#else
+               int accelerators = 3;
+#endif
+
+               wxAcceleratorEntry* accel = new wxAcceleratorEntry[accelerators];
+               accel[0].Set(wxACCEL_NORMAL, WXK_SPACE, ID_start_stop);
+               accel[1].Set(wxACCEL_NORMAL, WXK_LEFT, ID_back_frame);
+               accel[2].Set(wxACCEL_NORMAL, WXK_RIGHT, ID_forward_frame);
+#ifdef __WXOSX__
+               accel[3].Set(wxACCEL_CTRL, static_cast<int>('W'), ID_file_close);
+#endif
+               wxAcceleratorTable accel_table (accelerators, accel);
                SetAcceleratorTable (accel_table);
                delete[] accel;
-#endif
+
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::start_stop_pressed, this), ID_start_stop);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::back_frame, this), ID_back_frame);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::forward_frame, this), ID_forward_frame);
 
                UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
        }
@@ -170,33 +201,15 @@ public:
                }
 
                _film->examine_and_add_content (dcp, true);
-
-               JobManager* jm = JobManager::instance ();
-
-               wxProgressDialog* progress = new wxProgressDialog (_("DCP-o-matic Player"), _("Loading DCP"));
-
-               while (jm->work_to_do() || signal_manager->ui_idle()) {
-                       dcpomatic_sleep (1);
-                       progress->Pulse ();
-               }
-
-               progress->Destroy ();
-
-               DCPOMATIC_ASSERT (!jm->get().empty());
-
-               shared_ptr<Job> last = jm->get().back();
-               if (last->finished_in_error()) {
-                       error_dialog(this, std_to_wx(last->error_summary()) + ".\n", std_to_wx(last->error_details()));
+               bool const ok = progress (_("Loading DCP"));
+               if (!ok || !report_errors_from_last_job()) {
                        return;
                }
 
-               if (dcp->subtitle) {
-                       dcp->subtitle->set_use (true);
-               }
+               setup_from_dcp (dcp);
 
-               Ratio const * r = Ratio::nearest_from_ratio(dcp->video->size().ratio());
-               if (r) {
-                       _film->set_container(r);
+               if (dcp->three_d()) {
+                       _film->set_three_d (true);
                }
 
                _viewer->set_film (_film);
@@ -206,6 +219,21 @@ public:
                Config::instance()->add_to_player_history (dir);
 
                set_menu_sensitivity ();
+
+               wxMenuItemList old = _cpl_menu->GetMenuItems();
+               for (wxMenuItemList::iterator i = old.begin(); i != old.end(); ++i) {
+                       _cpl_menu->Remove (*i);
+               }
+
+               DCPExaminer ex (dcp);
+               int id = ID_view_cpl;
+               BOOST_FOREACH (shared_ptr<dcp::CPL> i, ex.cpls()) {
+                       wxMenuItem* j = _cpl_menu->AppendRadioItem(id, i->id());
+                       if (!dcp->cpl() || i->id() == *dcp->cpl()) {
+                               j->Check(true);
+                       }
+                       ++id;
+               }
        }
 
 private:
@@ -236,8 +264,13 @@ private:
                edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
 #endif
 
+               _cpl_menu = new wxMenu;
+
                wxMenu* view = new wxMenu;
                optional<int> c = Config::instance()->decode_reduction();
+               _view_cpl = view->Append(ID_view_cpl, _("CPL"), _cpl_menu);
+               view->Append(ID_view_closed_captions, _("Closed captions..."));
+               view->AppendSeparator();
                view->AppendRadioItem(ID_view_scale_appropriate, _("Set decode resolution to match display"))->Check(!static_cast<bool>(c));
                view->AppendRadioItem(ID_view_scale_full, _("Decode at full resolution"))->Check(c && c.get() == 0);
                view->AppendRadioItem(ID_view_scale_half, _("Decode at half resolution"))->Check(c && c.get() == 1);
@@ -317,11 +350,12 @@ private:
                        shared_ptr<DCPContent> dcp = boost::dynamic_pointer_cast<DCPContent>(_film->content().front());
                        DCPOMATIC_ASSERT (dcp);
                        dcp->add_ov (wx_to_std(c->GetPath()));
-                       dcp->examine (shared_ptr<Job>());
-                       /* Maybe we just gained some subtitles */
-                       if (dcp->subtitle) {
-                               dcp->subtitle->set_use (true);
+                       JobManager::instance()->add(shared_ptr<Job>(new ExamineContentJob (_film, dcp)));
+                       bool const ok = progress (_("Loading DCP"));
+                       if (!ok || !report_errors_from_last_job()) {
+                               return;
                        }
+                       setup_from_dcp (dcp);
                }
 
                c->Destroy ();
@@ -380,6 +414,30 @@ private:
                _config_dialog->Show (this);
        }
 
+       void view_cpl (wxCommandEvent& ev)
+       {
+               shared_ptr<DCPContent> dcp = boost::dynamic_pointer_cast<DCPContent>(_film->content().front());
+               DCPOMATIC_ASSERT (dcp);
+               DCPExaminer ex (dcp);
+               int id = ev.GetId() - ID_view_cpl;
+               DCPOMATIC_ASSERT (id >= 0);
+               DCPOMATIC_ASSERT (id < int(ex.cpls().size()));
+               list<shared_ptr<dcp::CPL> > cpls = ex.cpls();
+               list<shared_ptr<dcp::CPL> >::iterator i = cpls.begin();
+               while (id > 0) {
+                       ++i;
+                       --id;
+               }
+
+               dcp->set_cpl ((*i)->id());
+               dcp->examine (shared_ptr<Job>());
+       }
+
+       void view_closed_captions ()
+       {
+               _viewer->show_closed_captions ();
+       }
+
        void tools_verify ()
        {
                shared_ptr<DCPContent> dcp = boost::dynamic_pointer_cast<DCPContent>(_film->content().front());
@@ -387,21 +445,16 @@ private:
 
                JobManager* jm = JobManager::instance ();
                jm->add (shared_ptr<Job> (new VerifyDCPJob (dcp->directories())));
-
-               wxProgressDialog* progress = new wxProgressDialog (_("DCP-o-matic Player"), _("Verifying DCP"));
-
-               while (jm->work_to_do() || signal_manager->ui_idle()) {
-                       dcpomatic_sleep (1);
-                       progress->Pulse ();
+               bool const ok = progress (_("Verifying DCP"));
+               if (!ok) {
+                       return;
                }
 
-               progress->Destroy ();
-
                DCPOMATIC_ASSERT (!jm->get().empty());
                shared_ptr<VerifyDCPJob> last = dynamic_pointer_cast<VerifyDCPJob> (jm->get().back());
                DCPOMATIC_ASSERT (last);
 
-               VerifyDCPDialog* d = new VerifyDCPDialog (this, last->notes ());
+               VerifyDCPDialog* d = new VerifyDCPDialog (this, last);
                d->ShowModal ();
                d->Destroy ();
        }
@@ -507,12 +560,90 @@ private:
                _tools_verify->Enable (static_cast<bool>(_film));
                _file_add_ov->Enable (static_cast<bool>(_film));
                _file_add_kdm->Enable (static_cast<bool>(_film));
+               _view_cpl->Enable (static_cast<bool>(_film));
+       }
+
+       void start_stop_pressed ()
+       {
+               if (_viewer->playing()) {
+                       _viewer->stop();
+               } else {
+                       _viewer->start();
+               }
+       }
+
+       void back_frame ()
+       {
+               _viewer->back_frame ();
+       }
+
+       void forward_frame ()
+       {
+               _viewer->forward_frame ();
+       }
+
+private:
+
+       /** @return false if the task was cancelled */
+       bool progress (wxString task)
+       {
+               JobManager* jm = JobManager::instance ();
+
+               wxProgressDialog* progress = new wxProgressDialog (_("DCP-o-matic Player"), task, 100, 0, wxPD_CAN_ABORT);
+
+               bool ok = true;
+
+               while (jm->work_to_do() || signal_manager->ui_idle()) {
+                       dcpomatic_sleep (1);
+                       if (!progress->Pulse()) {
+                               /* user pressed cancel */
+                               BOOST_FOREACH (shared_ptr<Job> i, jm->get()) {
+                                       i->cancel();
+                               }
+                               ok = false;
+                               break;
+                       }
+               }
+
+               progress->Destroy ();
+               return ok;
+       }
+
+       bool report_errors_from_last_job ()
+       {
+               JobManager* jm = JobManager::instance ();
+
+               DCPOMATIC_ASSERT (!jm->get().empty());
+
+               shared_ptr<Job> last = jm->get().back();
+               if (last->finished_in_error()) {
+                       error_dialog(this, std_to_wx(last->error_summary()) + ".\n", std_to_wx(last->error_details()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       void setup_from_dcp (shared_ptr<DCPContent> dcp)
+       {
+               BOOST_FOREACH (shared_ptr<TextContent> i, dcp->caption) {
+                       i->set_use (true);
+               }
+
+               if (dcp->video) {
+                       Ratio const * r = Ratio::nearest_from_ratio(dcp->video->size().ratio());
+                       if (r) {
+                               _film->set_container(r);
+                       }
+               }
        }
 
        bool _update_news_requested;
        PlayerInformation* _info;
        wxPreferencesEditor* _config_dialog;
        wxMenu* _file_menu;
+       wxMenuItem* _view_cpl;
+       wxMenu* _cpl_menu;
        int _history_items;
        int _history_position;
        wxMenuItem* _history_separator;
@@ -529,6 +660,32 @@ static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 
+class PlayServer : public Server
+{
+public:
+       explicit PlayServer (DOMFrame* frame)
+               : Server (PLAYER_PLAY_PORT)
+               , _frame (frame)
+       {}
+
+       void handle (shared_ptr<Socket> socket)
+       {
+               try {
+                       int const length = socket->read_uint32 ();
+                       scoped_array<char> buffer (new char[length]);
+                       socket->read (reinterpret_cast<uint8_t*> (buffer.get()), length);
+                       string s (buffer.get());
+                       signal_manager->when_idle (bind (&DOMFrame::load_dcp, _frame, s));
+                       socket->write (reinterpret_cast<uint8_t const *> ("OK"), 3);
+               } catch (...) {
+
+               }
+       }
+
+private:
+       DOMFrame* _frame;
+};
+
 /** @class App
  *  @brief The magic App class for wxWidgets.
  */
@@ -598,6 +755,9 @@ private:
 
                signal_manager = new wxSignalManager (this);
 
+               PlayServer* server = new PlayServer (_frame);
+               new thread (boost::bind (&PlayServer::run, server));
+
                if (!_dcp_to_load.empty() && boost::filesystem::is_directory (_dcp_to_load)) {
                        try {
                                _frame->load_dcp (_dcp_to_load);