{
_player_video_connection = _player->Video.connect (bind (&Butler::video, this, _1, _2));
_player_audio_connection = _player->Audio.connect (bind (&Butler::audio, this, _1, _2));
- _player_text_connection = _player->Text.connect (bind (&Butler::text, this, _1, _2, _3));
+ _player_text_connection = _player->Text.connect (bind (&Butler::text, this, _1, _2, _3, _4));
/* The butler must here about things first, otherwise it might not sort out suspensions in time for
get_video() to be called in response to this signal.
*/
return r;
}
-optional<pair<PlayerText, DCPTimePeriod> >
+optional<TextRingBuffers::Data>
Butler::get_closed_caption ()
{
boost::mutex::scoped_lock lm (_mutex);
}
void
-Butler::text (PlayerText pt, TextType type, DCPTimePeriod period)
+Butler::text (PlayerText pt, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
if (type != TEXT_CLOSED_CAPTION) {
return;
}
+ DCPOMATIC_ASSERT (track);
+
boost::mutex::scoped_lock lm2 (_buffers_mutex);
- _closed_caption.put (make_pair(pt, period));
+ _closed_caption.put (pt, *track, period);
}
void seek (DCPTime position, bool accurate);
std::pair<boost::shared_ptr<PlayerVideo>, DCPTime> get_video ();
boost::optional<DCPTime> get_audio (float* out, Frame frames);
- boost::optional<std::pair<PlayerText, DCPTimePeriod> > get_closed_caption ();
+ boost::optional<TextRingBuffers::Data> get_closed_caption ();
void disable_audio ();
void thread ();
void video (boost::shared_ptr<PlayerVideo> video, DCPTime time);
void audio (boost::shared_ptr<AudioBuffers> audio, DCPTime time);
- void text (PlayerText pt, TextType type, DCPTimePeriod period);
+ void text (PlayerText pt, TextType type, boost::optional<DCPTextTrack> track, DCPTimePeriod period);
bool should_run () const;
void prepare (boost::weak_ptr<PlayerVideo> video) const;
void player_change (ChangeType type, bool frequent);
/*
- 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.
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
/** Construct a DCP encoder.
* @param film Film that we are encoding.
{
_player_video_connection = _player->Video.connect (bind (&DCPEncoder::video, this, _1, _2));
_player_audio_connection = _player->Audio.connect (bind (&DCPEncoder::audio, this, _1, _2));
- _player_text_connection = _player->Text.connect (bind (&DCPEncoder::text, this, _1, _2, _3));
+ _player_text_connection = _player->Text.connect (bind (&DCPEncoder::text, this, _1, _2, _3, _4));
BOOST_FOREACH (shared_ptr<const Content> c, film->content ()) {
BOOST_FOREACH (shared_ptr<TextContent> i, c->text) {
}
void
-DCPEncoder::text (PlayerText data, TextType type, DCPTimePeriod period)
+DCPEncoder::text (PlayerText data, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
if (type == TEXT_CLOSED_CAPTION || _non_burnt_subtitles) {
- _writer->write (data, type, period);
+ _writer->write (data, type, track, period);
}
}
#include "types.h"
#include "player_text.h"
+#include "dcp_text_track.h"
#include "encoder.h"
#include <boost/weak_ptr.hpp>
void video (boost::shared_ptr<PlayerVideo>, DCPTime);
void audio (boost::shared_ptr<AudioBuffers>, DCPTime);
- void text (PlayerText, TextType, DCPTimePeriod);
+ void text (PlayerText, TextType, boost::optional<DCPTextTrack>, DCPTimePeriod);
boost::shared_ptr<Writer> _writer;
boost::shared_ptr<J2KEncoder> _j2k_encoder;
{
return !(a == b);
}
+
+bool
+operator< (DCPTextTrack const & a, DCPTextTrack const & b)
+{
+ if (a.name != b.name) {
+ return a.name < b.name;
+ }
+
+ return a.language < b.language;
+}
class DCPTextTrack
{
public:
+ DCPTextTrack () {}
DCPTextTrack (cxml::ConstNodePtr node);
DCPTextTrack (std::string name_, std::string language_);
bool operator== (DCPTextTrack const & a, DCPTextTrack const & b);
bool operator!= (DCPTextTrack const & a, DCPTextTrack const & b);
+bool operator< (DCPTextTrack const & a, DCPTextTrack const & b);
#endif
shared_ptr<Player> player (new Player (film, film->playlist ()));
player->set_ignore_video ();
player->set_ignore_audio ();
- player->Text.connect (bind(&Hints::text, this, _1, _2, _3));
+ player->Text.connect (bind(&Hints::text, this, _1, _2, _3, _4));
while (!player->pass ()) {
bind (boost::ref(Pulse));
}
}
void
-Hints::text (PlayerText text, TextType type, DCPTimePeriod period)
+Hints::text (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
if (type != TEXT_CLOSED_CAPTION) {
return;
#include "signaller.h"
#include "player_text.h"
#include "types.h"
+#include "dcp_text_track.h"
#include "dcpomatic_time.h"
#include <boost/weak_ptr.hpp>
#include <boost/signals2.hpp>
void thread ();
void stop_thread ();
void hint (std::string h);
- void text (PlayerText text, TextType type, DCPTimePeriod period);
+ void text (PlayerText text, TextType type, boost::optional<DCPTextTrack> track, DCPTimePeriod period);
boost::weak_ptr<const Film> _film;
boost::thread* _thread;
bool const always = (text->type() == TEXT_OPEN_SUBTITLE && _always_burn_open_subtitles);
if (text->use() && !always && !text->burn()) {
- Text (from.first, text->type(), DCPTimePeriod (from.second, dcp_to));
+ Text (from.first, text->type(), text->dcp_track().get_value_or(DCPTextTrack()), DCPTimePeriod (from.second, dcp_to));
}
}
/** Emitted when a text is ready. This signal may be emitted considerably
* after the corresponding Video.
*/
- boost::signals2::signal<void (PlayerText, TextType, DCPTimePeriod)> Text;
+ boost::signals2::signal<void (PlayerText, TextType, boost::optional<DCPTextTrack>, DCPTimePeriod)> Text;
private:
friend class PlayerWrapper;
using std::list;
using std::string;
using std::cout;
+using std::map;
using boost::shared_ptr;
using boost::optional;
using boost::dynamic_pointer_cast;
shared_ptr<T> reel_asset;
if (asset) {
-
boost::filesystem::path liberation_normal;
try {
liberation_normal = shared_path() / "LiberationSans-Regular.ttf";
boost::filesystem::create_directories (directory);
asset->write (directory / ("sub_" + asset->id() + ".xml"));
} else {
-
/* All our assets should be the same length; use the picture asset length here
as a reference to set the subtitle one. We'll use the duration rather than
the intrinsic duration; we don't care if the picture asset has been trimmed, we're
}
reel->add (reel_sound_asset);
- maybe_add_text<dcp::ReelSubtitleAsset> (_text_asset[TEXT_OPEN_SUBTITLE], reel_picture_asset->duration(), reel, refs, fonts, _film, _period);
- maybe_add_text<dcp::ReelClosedCaptionAsset> (_text_asset[TEXT_CLOSED_CAPTION], reel_picture_asset->duration(), reel, refs, fonts, _film, _period);
+ maybe_add_text<dcp::ReelSubtitleAsset> (_subtitle_asset, reel_picture_asset->duration(), reel, refs, fonts, _film, _period);
+ for (map<DCPTextTrack, shared_ptr<dcp::SubtitleAsset> >::const_iterator i = _closed_caption_assets.begin(); i != _closed_caption_assets.end(); ++i) {
+ maybe_add_text<dcp::ReelClosedCaptionAsset> (i->second, reel_picture_asset->duration(), reel, refs, fonts, _film, _period);
+ }
return reel;
}
}
void
-ReelWriter::write (PlayerText subs, TextType type, DCPTimePeriod period)
+ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
- if (!_text_asset[type]) {
+ shared_ptr<dcp::SubtitleAsset> asset;
+
+ switch (type) {
+ case TEXT_OPEN_SUBTITLE:
+ asset = _subtitle_asset;
+ break;
+ case TEXT_CLOSED_CAPTION:
+ DCPOMATIC_ASSERT (track);
+ asset = _closed_caption_assets[*track];
+ break;
+ default:
+ DCPOMATIC_ASSERT (false);
+ }
+
+ if (!asset) {
string lang = _film->subtitle_language ();
if (lang.empty ()) {
lang = "Unknown";
s->set_movie_title (_film->name ());
s->set_language (lang);
s->set_reel_number (raw_convert<string> (_reel_index + 1));
- _text_asset[type] = s;
+ asset = s;
} else {
shared_ptr<dcp::SMPTESubtitleAsset> s (new dcp::SMPTESubtitleAsset ());
s->set_content_title_text (_film->name ());
if (_film->encrypted ()) {
s->set_key (_film->key ());
}
- _text_asset[type] = s;
+ asset = s;
}
}
+ switch (type) {
+ case TEXT_OPEN_SUBTITLE:
+ _subtitle_asset = asset;
+ break;
+ case TEXT_CLOSED_CAPTION:
+ DCPOMATIC_ASSERT (track);
+ _closed_caption_assets[*track] = asset;
+ break;
+ default:
+ DCPOMATIC_ASSERT (false);
+ }
+
BOOST_FOREACH (StringText i, subs.string) {
/* XXX: couldn't / shouldn't we use period here rather than getting time from the subtitle? */
i.set_in (i.in() - dcp::Time (_period.from.seconds(), i.in().tcr));
i.set_out (i.out() - dcp::Time (_period.from.seconds(), i.out().tcr));
- _text_asset[type]->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
+ asset->add (shared_ptr<dcp::Subtitle>(new dcp::SubtitleString(i)));
}
BOOST_FOREACH (BitmapText i, subs.bitmap) {
- _text_asset[type]->add (
+ asset->add (
shared_ptr<dcp::Subtitle>(
new dcp::SubtitleImage(
i.image->as_png(),
#include "dcpomatic_time.h"
#include "referenced_reel_asset.h"
#include "player_text.h"
+#include "dcp_text_track.h"
#include <dcp/picture_asset_writer.h>
#include <boost/shared_ptr.hpp>
void fake_write (Frame frame, Eyes eyes, int size);
void repeat_write (Frame frame, Eyes eyes);
void write (boost::shared_ptr<const AudioBuffers> audio);
- void write (PlayerText text, TextType type, DCPTimePeriod period);
+ void write (PlayerText text, TextType type, boost::optional<DCPTextTrack> track, DCPTimePeriod period);
void finish ();
boost::shared_ptr<dcp::Reel> create_reel (std::list<ReferencedReelAsset> const & refs, std::list<boost::shared_ptr<Font> > const & fonts);
boost::shared_ptr<dcp::PictureAssetWriter> _picture_asset_writer;
boost::shared_ptr<dcp::SoundAsset> _sound_asset;
boost::shared_ptr<dcp::SoundAssetWriter> _sound_asset_writer;
- boost::shared_ptr<dcp::SubtitleAsset> _text_asset[TEXT_COUNT];
+ boost::shared_ptr<dcp::SubtitleAsset> _subtitle_asset;
+ std::map<DCPTextTrack, boost::shared_ptr<dcp::SubtitleAsset> > _closed_caption_assets;
static int const _info_size;
};
using boost::optional;
void
-TextRingBuffers::put (pair<PlayerText, DCPTimePeriod> text)
+TextRingBuffers::put (PlayerText text, DCPTextTrack track, DCPTimePeriod period)
{
boost::mutex::scoped_lock lm (_mutex);
- _data.push_back (text);
+ _data.push_back (Data(text, track, period));
}
-optional<pair<PlayerText, DCPTimePeriod> >
+optional<TextRingBuffers::Data>
TextRingBuffers::get ()
{
boost::mutex::scoped_lock lm (_mutex);
if (_data.empty ()) {
- return pair<PlayerText, DCPTimePeriod>();
+ return optional<Data>();
}
- pair<PlayerText, DCPTimePeriod> r = _data.front ();
+ Data r = _data.front ();
_data.pop_front ();
return r;
}
*/
+#ifndef DCPOMATIC_TEXT_RING_BUFFERS_H
+#define DCPOMATIC_TEXT_RING_BUFFERS_H
+
#include "player_text.h"
+#include "dcp_text_track.h"
#include <boost/thread.hpp>
#include <utility>
class TextRingBuffers
{
public:
- void put (std::pair<PlayerText, DCPTimePeriod> text);
- boost::optional<std::pair<PlayerText, DCPTimePeriod> > get ();
+ void put (PlayerText text, DCPTextTrack track, DCPTimePeriod period);
+
+ struct Data {
+ Data (PlayerText text_, DCPTextTrack track_, DCPTimePeriod period_)
+ : text (text_)
+ , track (track_)
+ , period (period_)
+ {}
+
+ PlayerText text;
+ DCPTextTrack track;
+ DCPTimePeriod period;
+ };
+
+ boost::optional<Data> get ();
void clear ();
private:
boost::mutex _mutex;
- std::list<std::pair<PlayerText, DCPTimePeriod> > _data;
+
+ std::list<Data> _data;
};
+
+#endif
/*
- 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.
using std::map;
using std::min;
using std::max;
+using std::vector;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
using dcp::Data;
Writer::Writer (shared_ptr<const Film> film, weak_ptr<Job> j)
and captions arrive to the Writer in sequence. This is not so for video.
*/
_audio_reel = _reels.begin ();
- for (int i = 0; i < TEXT_COUNT; ++i) {
- _text_reel[i] = _reels.begin ();
+ _subtitle_reel = _reels.begin ();
+ BOOST_FOREACH (DCPTextTrack i, _film->closed_caption_tracks()) {
+ _caption_reels[i] = _reels.begin ();
}
/* Check that the signer is OK if we need one */
return (frame != 0 && frame < reel.first_nonexistant_frame());
}
+/** @param track Closed caption track if type == TEXT_CLOSED_CAPTION */
void
-Writer::write (PlayerText text, TextType type, DCPTimePeriod period)
+Writer::write (PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
- while (_text_reel[type]->period().to <= period.from) {
- ++_text_reel[type];
- DCPOMATIC_ASSERT (_text_reel[type] != _reels.end());
+ vector<ReelWriter>::iterator* reel = 0;
+
+ switch (type) {
+ case TEXT_OPEN_SUBTITLE:
+ reel = &_subtitle_reel;
+ break;
+ case TEXT_CLOSED_CAPTION:
+ reel = &_caption_reels[*track];
+ break;
+ default:
+ DCPOMATIC_ASSERT (false);
}
- DCPOMATIC_ASSERT (_text_reel[type] != _reels.end());
+ while ((*reel)->period().to <= period.from) {
+ ++(*reel);
+ DCPOMATIC_ASSERT (*reel != _reels.end());
+ }
- _text_reel[type]->write (text, type, period);
+ (*reel)->write (text, type, track, period);
}
void
#include "types.h"
#include "player_text.h"
#include "exception_store.h"
+#include "dcp_text_track.h"
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/thread.hpp>
bool can_repeat (Frame) const;
void repeat (Frame, Eyes);
void write (boost::shared_ptr<const AudioBuffers>, DCPTime time);
- void write (PlayerText text, TextType type, DCPTimePeriod period);
+ void write (PlayerText text, TextType type, boost::optional<DCPTextTrack>, DCPTimePeriod period);
void write (std::list<boost::shared_ptr<Font> > fonts);
void write (ReferencedReelAsset asset);
void finish ();
boost::weak_ptr<Job> _job;
std::vector<ReelWriter> _reels;
std::vector<ReelWriter>::iterator _audio_reel;
- std::vector<ReelWriter>::iterator _text_reel[TEXT_COUNT];
+ std::vector<ReelWriter>::iterator _subtitle_reel;
+ std::map<DCPTextTrack, std::vector<ReelWriter>::iterator> _caption_reels;
/** our thread, or 0 */
boost::thread* _thread;
void
ClosedCaptionsDialog::update (DCPTime time)
{
- if (_current_in_lines && _current->second.to > time) {
+ if (_current_in_lines && _current->period.to > time) {
/* Current one is fine */
return;
}
- if (_current && _current->second.to < time) {
+ if (_current && _current->period.to < time) {
/* Current one has finished; clear out */
for (int j = 0; j < CLOSED_CAPTION_LINES; ++j) {
_lines[j] = "";
}
Refresh ();
- _current = optional<pair<PlayerText, DCPTimePeriod> >();
+ _current = optional<TextRingBuffers::Data>();
}
if (!_current) {
_current_in_lines = false;
}
- if (_current && _current->second.contains(time)) {
+ if (_current && _current->period.contains(time)) {
/* We need to set this new one up */
- list<StringText> to_show = _current->first.string;
+ list<StringText> to_show = _current->text.string;
for (int j = 0; j < CLOSED_CAPTION_LINES; ++j) {
_lines[j] = "";
void
ClosedCaptionsDialog::clear ()
{
- _current = optional<pair<PlayerText, DCPTimePeriod> >();
+ _current = optional<TextRingBuffers::Data>();
_current_in_lines = false;
Refresh ();
}
#include "lib/dcpomatic_time.h"
#include "lib/player.h"
+#include "lib/text_ring_buffers.h"
#include <wx/wx.h>
class Butler;
private:
void paint ();
- boost::optional<std::pair<PlayerText, DCPTimePeriod> > _current;
+ boost::optional<TextRingBuffers::Data> _current;
bool _current_in_lines;
std::vector<wxString> _lines;
boost::weak_ptr<Butler> _butler;
BOOST_REQUIRE_EQUAL (check.cpls().size(), 1);
BOOST_REQUIRE_EQUAL (check.cpls().front()->reels().size(), 1);
+ std::cout << !check.cpls().front()->reels().front()->closed_captions().size() << "\n";
BOOST_REQUIRE_EQUAL (!check.cpls().front()->reels().front()->closed_captions().size(), 3);
}
struct Sub {
PlayerText text;
TextType type;
+ optional<DCPTextTrack> track;
DCPTimePeriod period;
};
static void
-store (list<Sub>* out, PlayerText text, TextType type, DCPTimePeriod period)
+store (list<Sub>* out, PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
{
Sub s;
s.text = text;
s.type = type;
+ s.track = track;
s.period = period;
out->push_back (s);
}
player->set_ignore_audio ();
list<Sub> out;
- player->Text.connect (bind (&store, &out, _1, _2, _3));
+ player->Text.connect (bind (&store, &out, _1, _2, _3, _4));
while (!player->pass ()) {}
BOOST_CHECK_EQUAL (out.size(), 6);