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.
*/
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;
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 ();
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;
};
, _y_scale (1)
, _line_spacing (1)
, _outline_width (2)
- , _type (CAPTION_OPEN)
+ , _type (original_type)
, _original_type (original_type)
{
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));
}
}
}
}
- optional<PositionImage> captions = captions_for_frame (time);
+ optional<PositionImage> captions = open_captions_for_frame (time);
if (captions) {
pv->set_caption (captions.get ());
}
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.
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);
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,
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);
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);
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());
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) {
*/
-#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;
};
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);
}
}
}
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;
}
*/
#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;
};
+++ /dev/null
-/*
- 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 ();
-}
+++ /dev/null
-/*
- 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;
-};
#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"
if (!_film) {
_player.reset ();
+ _closed_captions_dialog->set_player (_player);
recreate_butler ();
_frame.reset ();
refresh_panel ();
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;
refresh_panel ();
- _closed_captions_dialog->refresh (time());
+ _closed_captions_dialog->update (time());
}
void
{
_closed_captions_dialog->Show();
}
-
-void
-FilmViewer::caption (PlayerCaption c, CaptionType t, DCPTimePeriod p)
-{
- if (t == CAPTION_CLOSED) {
- _closed_captions_dialog->caption (c, p);
- }
-}
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;