From ded71cffd18962ebb6b9611a5eb6dfafe9e8e4ec Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Mon, 23 Jul 2018 01:21:07 +0100 Subject: [PATCH] Get ccaps by asking the Player, rather than by listening to its emissions, which is slightly cleaner and works when subtitles are emitted with an unknown end time. Also add CCAPs to the player. --- src/lib/active_captions.cc | 40 +++++++-- src/lib/active_captions.h | 3 + src/lib/caption_content.cc | 2 +- src/lib/player.cc | 43 ++++++---- src/lib/player.h | 4 +- src/tools/dcpomatic_player.cc | 15 ++-- src/wx/closed_captions_dialog.cc | 34 +++----- src/wx/closed_captions_dialog.h | 15 ++-- src/wx/closed_captions_view.cc | 143 ------------------------------- src/wx/closed_captions_view.h | 42 --------- src/wx/film_viewer.cc | 16 ++-- src/wx/film_viewer.h | 1 - 12 files changed, 100 insertions(+), 258 deletions(-) delete mode 100644 src/wx/closed_captions_view.cc delete mode 100644 src/wx/closed_captions_view.h diff --git a/src/lib/active_captions.cc b/src/lib/active_captions.cc index b4252e0c3..1d3a53609 100644 --- a/src/lib/active_captions.cc +++ b/src/lib/active_captions.cc @@ -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& pc, list p) const +{ + BOOST_FOREACH (Period i, p) { + DCPTimePeriod test (i.from, i.to.get_value_or(DCPTime::max())); + optional overlap = period.overlap (test); + if (overlap && overlap->duration() > DCPTime(period.duration().get() / 2)) { + pc.push_back (i.subs); + } + } +} + +list +ActiveCaptions::get (DCPTimePeriod period) const +{ + list ps; + + for (Map::const_iterator i = _data.begin(); i != _data.end(); ++i) { + + shared_ptr 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 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; diff --git a/src/lib/active_captions.h b/src/lib/active_captions.h index 10b0b5da9..8e38564f5 100644 --- a/src/lib/active_captions.h +++ b/src/lib/active_captions.h @@ -36,6 +36,7 @@ class CaptionContent; class ActiveCaptions : public boost::noncopyable { public: + std::list get (DCPTimePeriod period) const; std::list get_burnt (DCPTimePeriod period, bool always_burn_captions) const; void clear_before (DCPTime time); void clear (); @@ -61,5 +62,7 @@ private: typedef std::map, std::list > Map; + void add (DCPTimePeriod period, std::list& pc, std::list p) const; + Map _data; }; diff --git a/src/lib/caption_content.cc b/src/lib/caption_content.cc index 37a3e7c70..bbb1bacf3 100644 --- a/src/lib/caption_content.cc +++ b/src/lib/caption_content.cc @@ -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) { diff --git a/src/lib/player.cc b/src/lib/player.cc index dfd309774..4635233ff 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -676,29 +676,34 @@ Player::pass () return done; } +list +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 -Player::captions_for_frame (DCPTime time) const +Player::open_captions_for_frame (DCPTime time) const { list 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 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 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 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 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 pv, DCPTime time) } } - optional captions = captions_for_frame (time); + optional captions = open_captions_for_frame (time); if (captions) { pv->set_caption (captions.get ()); } diff --git a/src/lib/player.h b/src/lib/player.h index d54d927cd..8b85a011f 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -86,6 +86,8 @@ public: DCPTime content_time_to_dcp (boost::shared_ptr content, ContentTime t); + std::list 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, DCPTime> discard_audio ( boost::shared_ptr audio, DCPTime time, DCPTime discard_to ) const; - boost::optional captions_for_frame (DCPTime time) const; + boost::optional open_captions_for_frame (DCPTime time) const; void emit_video (boost::shared_ptr pv, DCPTime time); void do_emit_video (boost::shared_ptr pv, DCPTime time); void emit_audio (boost::shared_ptr data, DCPTime time); diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc index d357e566b..e5745403d 100644 --- a/src/tools/dcpomatic_player.cc +++ b/src/tools/dcpomatic_player.cc @@ -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(0)), ID_view_scale_full); Bind (wxEVT_MENU, boost::bind (&DOMFrame::set_decode_reduction, this, optional(1)), ID_view_scale_half); @@ -267,6 +269,7 @@ private: wxMenu* view = new wxMenu; optional 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(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()); } + void view_closed_captions () + { + _viewer->show_closed_captions (); + } + void tools_verify () { shared_ptr dcp = boost::dynamic_pointer_cast(_film->content().front()); @@ -619,10 +627,7 @@ private: void setup_from_dcp (shared_ptr dcp) { BOOST_FOREACH (shared_ptr 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) { diff --git a/src/wx/closed_captions_dialog.cc b/src/wx/closed_captions_dialog.cc index 3463ac27a..0b2e63035 100644 --- a/src/wx/closed_captions_dialog.cc +++ b/src/wx/closed_captions_dialog.cc @@ -18,12 +18,15 @@ */ -#include "closed_captions_view.h" +#include "closed_captions_dialog.h" +#include "lib/text_caption.h" #include 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.lock (); + DCPOMATIC_ASSERT (player); list to_show; - list::iterator i = _captions.begin (); - while (i != _captions.end ()) { - if (time > i->second.to) { - list::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) { - _captions.clear (); - Refresh (); + _player = player; } diff --git a/src/wx/closed_captions_dialog.h b/src/wx/closed_captions_dialog.h index ca19cc95a..a599bc703 100644 --- a/src/wx/closed_captions_dialog.h +++ b/src/wx/closed_captions_dialog.h @@ -19,24 +19,25 @@ */ #include "lib/dcpomatic_time.h" -#include "lib/player_caption.h" +#include "lib/player.h" #include -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); private: void paint (); - typedef std::pair Caption; - std::list _captions; std::vector _lines; + boost::weak_ptr _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 index be520057b..000000000 --- a/src/wx/closed_captions_view.cc +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright (C) 2018 Carl Hetherington - - 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 . - -*/ - -#include "closed_captions_view.h" -#include - -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 to_show; - list::iterator i = _captions.begin (); - while (i != _captions.end ()) { - if (time > i->second.to) { - list::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::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 index 9469b975c..000000000 --- a/src/wx/closed_captions_view.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2018 Carl Hetherington - - 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 . - -*/ - -#include "lib/dcpomatic_time.h" -#include "lib/player_caption.h" -#include - -class ClosedCaptionsDialog : public wxDialog -{ -public: - ClosedCaptionsDialog (wxWindow* parent); - - void refresh (DCPTime); - void caption (PlayerCaption, DCPTimePeriod); - void clear (); - -private: - void paint (); - - typedef std::pair Caption; - std::list _captions; - std::vector _lines; - static int const _num_lines; - static int const _num_chars_per_line; -}; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 9c714b562..651196d3c 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -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) 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) 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); - } -} diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 6825ef2c0..266509a44 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -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; boost::shared_ptr _player; -- 2.30.2