Very basic closed caption viewer.
authorCarl Hetherington <cth@carlh.net>
Sun, 22 Jul 2018 23:09:35 +0000 (00:09 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 22 Jul 2018 23:13:56 +0000 (00:13 +0100)
src/tools/dcpomatic.cc
src/wx/caption_panel.cc
src/wx/closed_captions_dialog.cc [new file with mode: 0644]
src/wx/closed_captions_dialog.h [new file with mode: 0644]
src/wx/closed_captions_view.cc [new file with mode: 0644]
src/wx/closed_captions_view.h [new file with mode: 0644]
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/wscript

index 416bdf6c4168afa0d733f0e7384fffae5a9cd129..b6aa2188a3b1230d3b68c8019217ecf73e378096 100644 (file)
@@ -212,7 +212,8 @@ enum {
        ID_jobs_send_dcp_to_tms,
        ID_jobs_show_dcp,
        ID_jobs_open_dcp_in_player,
-       ID_tools_video_waveform,
+       ID_view_closed_captions,
+       ID_view_video_waveform,
        ID_tools_hints,
        ID_tools_encoding_servers,
        ID_tools_manage_templates,
@@ -297,7 +298,8 @@ public:
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_send_dcp_to_tms, this),    ID_jobs_send_dcp_to_tms);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_show_dcp, this),           ID_jobs_show_dcp);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_open_dcp_in_player, this), ID_jobs_open_dcp_in_player);
-               Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_video_waveform, this),    ID_tools_video_waveform);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_closed_captions, this),    ID_view_closed_captions);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_video_waveform, this),     ID_view_video_waveform);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_hints, this),             ID_tools_hints);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_encoding_servers, this),  ID_tools_encoding_servers);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_manage_templates, this),  ID_tools_manage_templates);
@@ -424,8 +426,10 @@ public:
                _film = film;
                _film_viewer->set_film (_film);
                _film_editor->set_film (_film);
-               delete _video_waveform_dialog;
-               _video_waveform_dialog = 0;
+               if (_video_waveform_dialog) {
+                       _video_waveform_dialog->Destroy ();
+                       _video_waveform_dialog = 0;
+               }
                set_menu_sensitivity ();
                if (_film->directory()) {
                        Config::instance()->add_to_history (_film->directory().get());
@@ -882,7 +886,12 @@ private:
 #endif
        }
 
-       void tools_video_waveform ()
+       void view_closed_captions ()
+       {
+               _film_viewer->show_closed_captions ();
+       }
+
+       void view_video_waveform ()
        {
                if (!_video_waveform_dialog) {
                        _video_waveform_dialog = new VideoWaveformDialog (this, _film, _film_viewer);
@@ -1141,8 +1150,11 @@ private:
                add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
                add_item (jobs_menu, _("Open DCP in &player"), ID_jobs_open_dcp_in_player, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
 
+               wxMenu* view = new wxMenu;
+               add_item (view, _("Closed captions..."), ID_view_closed_captions, NEEDS_FILM);
+               add_item (view, _("Video waveform..."), ID_view_video_waveform, NEEDS_FILM);
+
                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, _("Manage templates..."), ID_tools_manage_templates, 0);
@@ -1162,6 +1174,7 @@ private:
                m->Append (edit, _("&Edit"));
                m->Append (content, _("&Content"));
                m->Append (jobs_menu, _("&Jobs"));
+               m->Append (view, _("&View"));
                m->Append (tools, _("&Tools"));
                m->Append (help, _("&Help"));
        }
index 3957c2a457b3b9937e8ca8c945edbefd37ee7107..1d2f59258c824423439f1899177326b5741b0910 100644 (file)
@@ -40,6 +40,7 @@
 using std::vector;
 using std::string;
 using std::list;
+using std::cout;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 
diff --git a/src/wx/closed_captions_dialog.cc b/src/wx/closed_captions_dialog.cc
new file mode 100644 (file)
index 0000000..be52005
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+    Copyright (C) 2018 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 "closed_captions_view.h"
+#include <boost/bind.hpp>
+
+using std::list;
+using std::cout;
+using std::make_pair;
+
+int const ClosedCaptionsDialog::_num_lines = 3;
+int const ClosedCaptionsDialog::_num_chars_per_line = 30;
+
+ClosedCaptionsDialog::ClosedCaptionsDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Closed captions"), wxDefaultPosition, wxDefaultSize,
+#ifdef DCPOMATIC_OSX
+               /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+                  the window above all others (and not just our own) it's better than nothing for now.
+               */
+               wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+               wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+               )
+
+{
+       _lines.resize (_num_lines);
+       Bind (wxEVT_PAINT, boost::bind (&ClosedCaptionsDialog::paint, this));
+}
+
+void
+ClosedCaptionsDialog::paint ()
+{
+       wxPaintDC dc (this);
+       dc.SetBackground (*wxBLACK_BRUSH);
+       dc.Clear ();
+       dc.SetTextForeground (*wxWHITE);
+
+       /* Choose a font which fits vertically */
+       int const line_height = dc.GetSize().GetHeight() / _num_lines;
+       wxFont font (*wxNORMAL_FONT);
+       font.SetPixelSize (wxSize (0, line_height * 0.8));
+       dc.SetFont (font);
+
+       for (int i = 0; i < _num_lines; ++i) {
+               if (_lines[i].IsEmpty()) {
+                       dc.DrawText (wxString::Format("Line %d", i + 1), 8, line_height * i);
+               } else {
+                       dc.DrawText (_lines[i], 8, line_height * i);
+               }
+       }
+}
+
+class ClosedCaptionSorter
+{
+public:
+       bool operator() (TextCaption const & a, TextCaption const & b)
+       {
+               return from_top(a) < from_top(b);
+       }
+
+private:
+       float from_top (TextCaption const & c) const
+       {
+               switch (c.v_align()) {
+               case dcp::VALIGN_TOP:
+                       return c.v_position();
+               case dcp::VALIGN_CENTER:
+                       return c.v_position() + 0.5;
+               case dcp::VALIGN_BOTTOM:
+                       return 1.0 - c.v_position();
+               }
+               DCPOMATIC_ASSERT (false);
+               return 0;
+       }
+};
+
+void
+ClosedCaptionsDialog::refresh (DCPTime time)
+{
+       list<TextCaption> to_show;
+       list<Caption>::iterator i = _captions.begin ();
+       while (i != _captions.end ()) {
+               if (time > i->second.to) {
+                       list<Caption>::iterator tmp = i;
+                       ++i;
+                       _captions.erase (tmp);
+               } else if (i->second.contains (time)) {
+                       BOOST_FOREACH (TextCaption j, i->first.text) {
+                               to_show.push_back (j);
+                       }
+                       ++i;
+               } else {
+                       ++i;
+               }
+       }
+
+       for (int j = 0; j < _num_lines; ++j) {
+               _lines[j] = "";
+       }
+
+       to_show.sort (ClosedCaptionSorter());
+
+       list<TextCaption>::const_iterator j = to_show.begin();
+       int k = 0;
+       while (j != to_show.end() && k < _num_lines) {
+               _lines[k] = j->text();
+               ++j;
+               ++k;
+       }
+
+       Refresh ();
+}
+
+void
+ClosedCaptionsDialog::caption (PlayerCaption caption, DCPTimePeriod period)
+{
+       _captions.push_back (make_pair (caption, period));
+}
+
+void
+ClosedCaptionsDialog::clear ()
+{
+       _captions.clear ();
+       Refresh ();
+}
diff --git a/src/wx/closed_captions_dialog.h b/src/wx/closed_captions_dialog.h
new file mode 100644 (file)
index 0000000..ca19cc9
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+    Copyright (C) 2018 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 "lib/dcpomatic_time.h"
+#include "lib/player_caption.h"
+#include <wx/wx.h>
+
+class ClosedCaptionsView : public wxDialog
+{
+public:
+       ClosedCaptionsView (wxWindow* parent);
+
+       void refresh (DCPTime);
+       void caption (PlayerCaption, DCPTimePeriod);
+       void clear ();
+
+private:
+       void paint ();
+
+       typedef std::pair<PlayerCaption, DCPTimePeriod> Caption;
+       std::list<Caption> _captions;
+       std::vector<wxString> _lines;
+       static int const _num_lines;
+       static int const _num_chars_per_line;
+};
diff --git a/src/wx/closed_captions_view.cc b/src/wx/closed_captions_view.cc
new file mode 100644 (file)
index 0000000..be52005
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+    Copyright (C) 2018 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 "closed_captions_view.h"
+#include <boost/bind.hpp>
+
+using std::list;
+using std::cout;
+using std::make_pair;
+
+int const ClosedCaptionsDialog::_num_lines = 3;
+int const ClosedCaptionsDialog::_num_chars_per_line = 30;
+
+ClosedCaptionsDialog::ClosedCaptionsDialog (wxWindow* parent)
+       : wxDialog (parent, wxID_ANY, _("Closed captions"), wxDefaultPosition, wxDefaultSize,
+#ifdef DCPOMATIC_OSX
+               /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+                  the window above all others (and not just our own) it's better than nothing for now.
+               */
+               wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+               wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+               )
+
+{
+       _lines.resize (_num_lines);
+       Bind (wxEVT_PAINT, boost::bind (&ClosedCaptionsDialog::paint, this));
+}
+
+void
+ClosedCaptionsDialog::paint ()
+{
+       wxPaintDC dc (this);
+       dc.SetBackground (*wxBLACK_BRUSH);
+       dc.Clear ();
+       dc.SetTextForeground (*wxWHITE);
+
+       /* Choose a font which fits vertically */
+       int const line_height = dc.GetSize().GetHeight() / _num_lines;
+       wxFont font (*wxNORMAL_FONT);
+       font.SetPixelSize (wxSize (0, line_height * 0.8));
+       dc.SetFont (font);
+
+       for (int i = 0; i < _num_lines; ++i) {
+               if (_lines[i].IsEmpty()) {
+                       dc.DrawText (wxString::Format("Line %d", i + 1), 8, line_height * i);
+               } else {
+                       dc.DrawText (_lines[i], 8, line_height * i);
+               }
+       }
+}
+
+class ClosedCaptionSorter
+{
+public:
+       bool operator() (TextCaption const & a, TextCaption const & b)
+       {
+               return from_top(a) < from_top(b);
+       }
+
+private:
+       float from_top (TextCaption const & c) const
+       {
+               switch (c.v_align()) {
+               case dcp::VALIGN_TOP:
+                       return c.v_position();
+               case dcp::VALIGN_CENTER:
+                       return c.v_position() + 0.5;
+               case dcp::VALIGN_BOTTOM:
+                       return 1.0 - c.v_position();
+               }
+               DCPOMATIC_ASSERT (false);
+               return 0;
+       }
+};
+
+void
+ClosedCaptionsDialog::refresh (DCPTime time)
+{
+       list<TextCaption> to_show;
+       list<Caption>::iterator i = _captions.begin ();
+       while (i != _captions.end ()) {
+               if (time > i->second.to) {
+                       list<Caption>::iterator tmp = i;
+                       ++i;
+                       _captions.erase (tmp);
+               } else if (i->second.contains (time)) {
+                       BOOST_FOREACH (TextCaption j, i->first.text) {
+                               to_show.push_back (j);
+                       }
+                       ++i;
+               } else {
+                       ++i;
+               }
+       }
+
+       for (int j = 0; j < _num_lines; ++j) {
+               _lines[j] = "";
+       }
+
+       to_show.sort (ClosedCaptionSorter());
+
+       list<TextCaption>::const_iterator j = to_show.begin();
+       int k = 0;
+       while (j != to_show.end() && k < _num_lines) {
+               _lines[k] = j->text();
+               ++j;
+               ++k;
+       }
+
+       Refresh ();
+}
+
+void
+ClosedCaptionsDialog::caption (PlayerCaption caption, DCPTimePeriod period)
+{
+       _captions.push_back (make_pair (caption, period));
+}
+
+void
+ClosedCaptionsDialog::clear ()
+{
+       _captions.clear ();
+       Refresh ();
+}
diff --git a/src/wx/closed_captions_view.h b/src/wx/closed_captions_view.h
new file mode 100644 (file)
index 0000000..9469b97
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+    Copyright (C) 2018 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 "lib/dcpomatic_time.h"
+#include "lib/player_caption.h"
+#include <wx/wx.h>
+
+class ClosedCaptionsDialog : public wxDialog
+{
+public:
+       ClosedCaptionsDialog (wxWindow* parent);
+
+       void refresh (DCPTime);
+       void caption (PlayerCaption, DCPTimePeriod);
+       void clear ();
+
+private:
+       void paint ();
+
+       typedef std::pair<PlayerCaption, DCPTimePeriod> Caption;
+       std::list<Caption> _captions;
+       std::vector<wxString> _lines;
+       static int const _num_lines;
+       static int const _num_chars_per_line;
+};
index 18d16a461789c9bd93b6e1784d0718eefcb5d184..9c714b5622ccc79cbd2bfdaa977f17626d61f0be 100644 (file)
@@ -26,6 +26,7 @@
 #include "playhead_to_timecode_dialog.h"
 #include "playhead_to_frame_dialog.h"
 #include "wx_util.h"
+#include "closed_captions_view.h"
 #include "lib/film.h"
 #include "lib/ratio.h"
 #include "lib/util.h"
@@ -94,6 +95,7 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        , _playing (false)
        , _latency_history_count (0)
        , _dropped (0)
+       , _closed_captions_dialog (new ClosedCaptionsDialog(GetParent()))
 {
 #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
@@ -194,6 +196,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
        _film = film;
 
        _frame.reset ();
+       _closed_captions_dialog->clear ();
 
        update_position_slider ();
        update_position_label ();
@@ -223,6 +226,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
 
        _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1, _2));
+       _player->Caption.connect (boost::bind (&FilmViewer::caption, this, _1, _2, _3));
 
        /* Keep about 1 second's worth of history samples */
        _latency_history_count = _film->audio_frame_rate() / _audio_block_size;
@@ -349,6 +353,8 @@ FilmViewer::display_player_video ()
        _inter_size = _player_video.first->inter_size ();
 
        refresh_panel ();
+
+       _closed_captions_dialog->refresh (time());
 }
 
 void
@@ -811,6 +817,7 @@ FilmViewer::seek (DCPTime t, bool accurate)
 
        bool const was_running = stop ();
 
+       _closed_captions_dialog->clear ();
        _butler->seek (t, accurate);
        get ();
 
@@ -935,3 +942,17 @@ FilmViewer::one_video_frame () const
 {
        return DCPTime::from_frames (1, _film->video_frame_rate());
 }
+
+void
+FilmViewer::show_closed_captions ()
+{
+       _closed_captions_dialog->Show();
+}
+
+void
+FilmViewer::caption (PlayerCaption c, CaptionType t, DCPTimePeriod p)
+{
+       if (t == CAPTION_CLOSED) {
+               _closed_captions_dialog->caption (c, p);
+       }
+}
index 385f6142becb1f43e77ed30af41b81e15b1d26b6..6825ef2c04185bb5186fc092672f694ebad77ca4 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -24,6 +24,7 @@
 
 #include "lib/film.h"
 #include "lib/config.h"
+#include "lib/player_caption.h"
 #include <RtAudio.h>
 #include <wx/wx.h>
 
@@ -34,6 +35,7 @@ class RGBPlusAlphaImage;
 class PlayerVideo;
 class Player;
 class Butler;
+class ClosedCaptionsDialog;
 
 /** @class FilmViewer
  *  @brief A wx widget to view a preview of a Film.
@@ -78,6 +80,8 @@ public:
 
        int audio_callback (void* out, unsigned int frames);
 
+       void show_closed_captions ();
+
        boost::signals2::signal<void (boost::weak_ptr<PlayerVideo>)> ImageChanged;
 
 private:
@@ -112,6 +116,7 @@ private:
        DCPTime time () const;
        Frame average_latency () const;
        DCPTime one_video_frame () const;
+       void caption (PlayerCaption caption, CaptionType type, DCPTimePeriod period);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
@@ -160,5 +165,7 @@ private:
        int _dropped;
        boost::optional<int> _dcp_decode_reduction;
 
+       ClosedCaptionsDialog* _closed_captions_dialog;
+
        boost::signals2::scoped_connection _config_changed_connection;
 };
index ec75bd99cf65bc83c11dfe9b7a375966a16fde0f..5668a7558bf22854098dda8ea5a06c3a0656ccee 100644 (file)
@@ -45,6 +45,7 @@ sources = """
           content_panel.cc
           content_properties_dialog.cc
           content_sub_panel.cc
+          closed_captions_dialog.cc
           dcp_panel.cc
           email_dialog.cc
           image_sequence_dialog.cc