Get ccaps by asking the Player, rather than by listening to its emissions,
authorCarl Hetherington <cth@carlh.net>
Mon, 23 Jul 2018 00:21:07 +0000 (01:21 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 23 Jul 2018 00:21:07 +0000 (01:21 +0100)
which is slightly cleaner and works when subtitles are emitted with an
unknown end time.  Also add CCAPs to the player.

12 files changed:
src/lib/active_captions.cc
src/lib/active_captions.h
src/lib/caption_content.cc
src/lib/player.cc
src/lib/player.h
src/tools/dcpomatic_player.cc
src/wx/closed_captions_dialog.cc
src/wx/closed_captions_dialog.h
src/wx/closed_captions_view.cc [deleted file]
src/wx/closed_captions_view.h [deleted file]
src/wx/film_viewer.cc
src/wx/film_viewer.h

index b4252e0c3ce7ba195a90b39e1a982fbb44d189b3..1d3a53609e39d87f27ce12aea1dec4a3055938b4 100644 (file)
@@ -30,7 +30,37 @@ using boost::weak_ptr;
 using boost::shared_ptr;
 using boost::optional;
 
-/** Get the subtitles that should be burnt into a given period.
+void
+ActiveCaptions::add (DCPTimePeriod period, list<PlayerCaption>& pc, list<Period> p) const
+{
+       BOOST_FOREACH (Period i, p) {
+               DCPTimePeriod test (i.from, i.to.get_value_or(DCPTime::max()));
+               optional<DCPTimePeriod> overlap = period.overlap (test);
+               if (overlap && overlap->duration() > DCPTime(period.duration().get() / 2)) {
+                       pc.push_back (i.subs);
+               }
+       }
+}
+
+list<PlayerCaption>
+ActiveCaptions::get (DCPTimePeriod period) const
+{
+       list<PlayerCaption> ps;
+
+       for (Map::const_iterator i = _data.begin(); i != _data.end(); ++i) {
+
+               shared_ptr<const CaptionContent> caption = i->first.lock ();
+               if (!caption || !caption->use()) {
+                       continue;
+               }
+
+               add (period, ps, i->second);
+       }
+
+       return ps;
+}
+
+/** Get the open captions that should be burnt into a given period.
  *  @param period Period of interest.
  *  @param always_burn_captions Always burn captions even if their content is not set to burn.
  */
@@ -51,13 +81,7 @@ ActiveCaptions::get_burnt (DCPTimePeriod period, bool always_burn_captions) cons
                        continue;
                }
 
-               BOOST_FOREACH (Period j, i->second) {
-                       DCPTimePeriod test (j.from, j.to.get_value_or(DCPTime::max()));
-                       optional<DCPTimePeriod> overlap = period.overlap (test);
-                       if (overlap && overlap->duration() > DCPTime(period.duration().get() / 2)) {
-                               ps.push_back (j.subs);
-                       }
-               }
+               add (period, ps, i->second);
        }
 
        return ps;
index 10b0b5da93cb8399779eba2257cd0d55a57c6498..8e38564f5b0f37acaeeb86b00ba029ef9d4a822e 100644 (file)
@@ -36,6 +36,7 @@ class CaptionContent;
 class ActiveCaptions : public boost::noncopyable
 {
 public:
+       std::list<PlayerCaption> get (DCPTimePeriod period) const;
        std::list<PlayerCaption> get_burnt (DCPTimePeriod period, bool always_burn_captions) const;
        void clear_before (DCPTime time);
        void clear ();
@@ -61,5 +62,7 @@ private:
 
        typedef std::map<boost::weak_ptr<const CaptionContent>, std::list<Period> > Map;
 
+       void add (DCPTimePeriod period, std::list<PlayerCaption>& pc, std::list<Period> p) const;
+
        Map _data;
 };
index 37a3e7c70f7b8b22058e896d70d08a39e7349779..bbb1bacf3d7a3eb6acb7efd7224890839db6b506 100644 (file)
@@ -67,7 +67,7 @@ CaptionContent::CaptionContent (Content* parent, CaptionType original_type)
        , _y_scale (1)
        , _line_spacing (1)
        , _outline_width (2)
-       , _type (CAPTION_OPEN)
+       , _type (original_type)
        , _original_type (original_type)
 {
 
index dfd3097745b8a15d58322149be6a394e01e26906..4635233fff759c1c0c7966332610f816738c003f 100644 (file)
@@ -676,29 +676,34 @@ Player::pass ()
        return done;
 }
 
+list<PlayerCaption>
+Player::closed_captions_for_frame (DCPTime time) const
+{
+       return _active_captions[CAPTION_CLOSED].get (
+               DCPTimePeriod(time, time + DCPTime::from_frames(1, _film->video_frame_rate()))
+               );
+}
+
+/** @return Open captions for the frame at the given time, converted to images */
 optional<PositionImage>
-Player::captions_for_frame (DCPTime time) const
+Player::open_captions_for_frame (DCPTime time) const
 {
        list<PositionImage> captions;
-
        int const vfr = _film->video_frame_rate();
 
-       for (int i = 0; i < CAPTION_COUNT; ++i) {
-               bool const always = i == CAPTION_OPEN && _always_burn_open_captions;
-               BOOST_FOREACH (
-                       PlayerCaption j,
-                       _active_captions[i].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), always)
-                       ) {
-
-                       /* Image subtitles */
-                       list<PositionImage> c = transform_bitmap_captions (j.image);
-                       copy (c.begin(), c.end(), back_inserter (captions));
-
-                       /* Text subtitles (rendered to an image) */
-                       if (!j.text.empty ()) {
-                               list<PositionImage> s = render_text (j.text, j.fonts, _video_container_size, time, vfr);
-                               copy (s.begin(), s.end(), back_inserter (captions));
-                       }
+       BOOST_FOREACH (
+               PlayerCaption j,
+               _active_captions[CAPTION_OPEN].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_captions)
+               ) {
+
+               /* Image subtitles */
+               list<PositionImage> c = transform_bitmap_captions (j.image);
+               copy (c.begin(), c.end(), back_inserter (captions));
+
+               /* Text subtitles (rendered to an image) */
+               if (!j.text.empty ()) {
+                       list<PositionImage> s = render_text (j.text, j.fonts, _video_container_size, time, vfr);
+                       copy (s.begin(), s.end(), back_inserter (captions));
                }
        }
 
@@ -1047,7 +1052,7 @@ Player::do_emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
                }
        }
 
-       optional<PositionImage> captions = captions_for_frame (time);
+       optional<PositionImage> captions = open_captions_for_frame (time);
        if (captions) {
                pv->set_caption (captions.get ());
        }
index d54d927cd65514c5803c888ca2758fbfc672f12e..8b85a011f24a5f8c12c2291da2ec4a9dbce4c8c5 100644 (file)
@@ -86,6 +86,8 @@ public:
 
        DCPTime content_time_to_dcp (boost::shared_ptr<Content> content, ContentTime t);
 
+       std::list<PlayerCaption> closed_captions_for_frame (DCPTime time) const;
+
        /** Emitted when something has changed such that if we went back and emitted
         *  the last frame again it would look different.  This is not emitted after
         *  a seek.
@@ -134,7 +136,7 @@ private:
        std::pair<boost::shared_ptr<AudioBuffers>, DCPTime> discard_audio (
                boost::shared_ptr<const AudioBuffers> audio, DCPTime time, DCPTime discard_to
                ) const;
-       boost::optional<PositionImage> captions_for_frame (DCPTime time) const;
+       boost::optional<PositionImage> open_captions_for_frame (DCPTime time) const;
        void emit_video (boost::shared_ptr<PlayerVideo> pv, DCPTime time);
        void do_emit_video (boost::shared_ptr<PlayerVideo> pv, DCPTime time);
        void emit_audio (boost::shared_ptr<AudioBuffers> data, DCPTime time);
index d357e566b4aecd2fae832c2bb0bedfe044408663..e5745403d0c60e343b46d7bc2082b33fe8c7d9a6 100644 (file)
@@ -82,7 +82,8 @@ enum {
        ID_file_close = 100,
        ID_view_cpl,
        /* Allow spare IDs for CPLs */
-       ID_view_scale_appropriate = 200,
+       ID_view_closed_captions = 200,
+       ID_view_scale_appropriate,
        ID_view_scale_full,
        ID_view_scale_half,
        ID_view_scale_quarter,
@@ -134,6 +135,7 @@ 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::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);
@@ -267,6 +269,7 @@ private:
                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);
@@ -430,6 +433,11 @@ private:
                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());
@@ -619,10 +627,7 @@ private:
        void setup_from_dcp (shared_ptr<DCPContent> dcp)
        {
                BOOST_FOREACH (shared_ptr<CaptionContent> i, dcp->caption) {
-                       /* XXX: we should offer the option to view closed captions */
-                       if (i->type() == CAPTION_OPEN) {
-                               i->set_use (true);
-                       }
+                       i->set_use (true);
                }
 
                if (dcp->video) {
index 3463ac27a7e1e35479dd3ae16c09180a1b5dc4fc..0b2e630357e60ed83a26c0a3d0d890687c4b6898 100644 (file)
 
 */
 
-#include "closed_captions_view.h"
+#include "closed_captions_dialog.h"
+#include "lib/text_caption.h"
 #include <boost/bind.hpp>
 
 using std::list;
 using std::cout;
 using std::make_pair;
+using boost::shared_ptr;
+using boost::weak_ptr;
 
 int const ClosedCaptionsDialog::_num_lines = 3;
 int const ClosedCaptionsDialog::_num_chars_per_line = 30;
@@ -89,22 +92,14 @@ private:
 };
 
 void
-ClosedCaptionsDialog::refresh (DCPTime time)
+ClosedCaptionsDialog::update (DCPTime time)
 {
+       shared_ptr<Player> player = _player.lock ();
+       DCPOMATIC_ASSERT (player);
        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;
+       BOOST_FOREACH (PlayerCaption i, player->closed_captions_for_frame(time)) {
+               BOOST_FOREACH (TextCaption j, i.text) {
+                       to_show.push_back (j);
                }
        }
 
@@ -126,14 +121,13 @@ ClosedCaptionsDialog::refresh (DCPTime time)
 }
 
 void
-ClosedCaptionsDialog::caption (PlayerCaption caption, DCPTimePeriod period)
+ClosedCaptionsDialog::clear ()
 {
-       _captions.push_back (make_pair (caption, period));
+       Refresh ();
 }
 
 void
-ClosedCaptionsDialog::clear ()
+ClosedCaptionsDialog::set_player (weak_ptr<Player> player)
 {
-       _captions.clear ();
-       Refresh ();
+       _player = player;
 }
index ca19cc95ad97b0205c3955502a24dded4f5cdf1f..a599bc7036f8d048bb421a64fe30a9e974a506d8 100644 (file)
 */
 
 #include "lib/dcpomatic_time.h"
-#include "lib/player_caption.h"
+#include "lib/player.h"
 #include <wx/wx.h>
 
-class ClosedCaptionsView : public wxDialog
+class Player;
+
+class ClosedCaptionsDialog : public wxDialog
 {
 public:
-       ClosedCaptionsView (wxWindow* parent);
+       ClosedCaptionsDialog (wxWindow* parent);
 
-       void refresh (DCPTime);
-       void caption (PlayerCaption, DCPTimePeriod);
+       void update (DCPTime);
        void clear ();
+       void set_player (boost::weak_ptr<Player>);
 
 private:
        void paint ();
 
-       typedef std::pair<PlayerCaption, DCPTimePeriod> Caption;
-       std::list<Caption> _captions;
        std::vector<wxString> _lines;
+       boost::weak_ptr<Player> _player;
        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
deleted file mode 100644 (file)
index be52005..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
-    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
deleted file mode 100644 (file)
index 9469b97..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-    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 9c714b5622ccc79cbd2bfdaa977f17626d61f0be..651196d3c34161277c5d654b4ecde92c73ec3ed6 100644 (file)
@@ -26,7 +26,7 @@
 #include "playhead_to_timecode_dialog.h"
 #include "playhead_to_frame_dialog.h"
 #include "wx_util.h"
-#include "closed_captions_view.h"
+#include "closed_captions_dialog.h"
 #include "lib/film.h"
 #include "lib/ratio.h"
 #include "lib/util.h"
@@ -203,6 +203,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
 
        if (!_film) {
                _player.reset ();
+               _closed_captions_dialog->set_player (_player);
                recreate_butler ();
                _frame.reset ();
                refresh_panel ();
@@ -221,12 +222,13 @@ FilmViewer::set_film (shared_ptr<Film> film)
                return;
        }
 
+       _closed_captions_dialog->set_player (_player);
+
        _player->set_always_burn_open_captions ();
        _player->set_play_referenced ();
 
        _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;
@@ -354,7 +356,7 @@ FilmViewer::display_player_video ()
 
        refresh_panel ();
 
-       _closed_captions_dialog->refresh (time());
+       _closed_captions_dialog->update (time());
 }
 
 void
@@ -948,11 +950,3 @@ 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 6825ef2c04185bb5186fc092672f694ebad77ca4..266509a4466ad1161234f919e09f07a8e362461e 100644 (file)
@@ -116,7 +116,6 @@ 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;