/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2020 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
+#include "atmos_decoder.h"
#include "player.h"
#include "film.h"
#include "audio_buffers.h"
#include "content_audio.h"
#include "dcp_content.h"
+#include "dcpomatic_log.h"
#include "job.h"
#include "image.h"
#include "raw_image_proxy.h"
#include "ratio.h"
#include "log.h"
-#include "render_subtitles.h"
+#include "render_text.h"
#include "config.h"
#include "content_video.h"
#include "player_video.h"
#include "decoder.h"
#include "video_decoder.h"
#include "audio_decoder.h"
-#include "subtitle_content.h"
-#include "subtitle_decoder.h"
+#include "text_content.h"
+#include "text_decoder.h"
#include "ffmpeg_content.h"
#include "audio_content.h"
-#include "content_subtitle.h"
#include "dcp_decoder.h"
#include "image_decoder.h"
#include "compose.hpp"
#include "shuffler.h"
+#include "timer.h"
#include <dcp/reel.h>
#include <dcp/reel_sound_asset.h>
#include <dcp/reel_subtitle_asset.h>
#include <dcp/reel_picture_asset.h>
-#include <boost/foreach.hpp>
+#include <dcp/reel_closed_caption_asset.h>
#include <stdint.h>
#include <algorithm>
#include <iostream>
#include "i18n.h"
-#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
-
-using std::list;
+using std::copy;
using std::cout;
-using std::min;
+using std::dynamic_pointer_cast;
+using std::list;
+using std::make_pair;
+using std::make_shared;
+using std::map;
using std::max;
using std::min;
-using std::vector;
+using std::min;
using std::pair;
-using std::map;
-using std::make_pair;
-using std::copy;
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::dynamic_pointer_cast;
+using std::shared_ptr;
+using std::vector;
+using std::weak_ptr;
using boost::optional;
using boost::scoped_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+using namespace dcpomatic;
int const PlayerProperty::VIDEO_CONTAINER_SIZE = 700;
int const PlayerProperty::PLAYLIST = 701;
int const PlayerProperty::FILM_CONTAINER = 702;
int const PlayerProperty::FILM_VIDEO_FRAME_RATE = 703;
int const PlayerProperty::DCP_DECODE_REDUCTION = 704;
+int const PlayerProperty::PLAYBACK_LENGTH = 705;
-Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
+Player::Player (shared_ptr<const Film> film)
: _film (film)
- , _playlist (playlist)
- , _have_valid_pieces (false)
- , _ignore_video (false)
- , _ignore_subtitle (false)
- , _always_burn_subtitles (false)
- , _fast (false)
- , _play_referenced (false)
+ , _suspended (0)
+ , _tolerant (film->tolerant())
, _audio_merger (_film->audio_frame_rate())
- , _shuffler (0)
{
- _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
- _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
- _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
+ construct ();
+}
+
+Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist_)
+ : _film (film)
+ , _playlist (playlist_)
+ , _suspended (0)
+ , _tolerant (film->tolerant())
+ , _audio_merger (_film->audio_frame_rate())
+{
+ construct ();
+}
+
+void
+Player::construct ()
+{
+ _film_changed_connection = _film->Change.connect (bind (&Player::film_change, this, _1, _2));
+ /* The butler must hear about this first, so since we are proxying this through to the butler we must
+ be first.
+ */
+ _playlist_change_connection = playlist()->Change.connect (bind (&Player::playlist_change, this, _1), boost::signals2::at_front);
+ _playlist_content_change_connection = playlist()->ContentChange.connect (bind(&Player::playlist_content_change, this, _1, _3, _4));
set_video_container_size (_film->frame_size ());
- film_changed (Film::AUDIO_PROCESSOR);
+ film_change (ChangeType::DONE, Film::AUDIO_PROCESSOR);
+ setup_pieces ();
seek (DCPTime (), true);
}
void
Player::setup_pieces ()
{
+ boost::mutex::scoped_lock lm (_mutex);
+ setup_pieces_unlocked ();
+}
+
+
+bool
+have_video (shared_ptr<const Content> content)
+{
+ return static_cast<bool>(content->video) && content->video->use();
+}
+
+bool
+have_audio (shared_ptr<const Content> content)
+{
+ return static_cast<bool>(content->audio);
+}
+
+void
+Player::setup_pieces_unlocked ()
+{
+ _playback_length = _playlist ? _playlist->length(_film) : _film->length();
+
+ auto old_pieces = _pieces;
_pieces.clear ();
delete _shuffler;
_shuffler = new Shuffler();
_shuffler->Video.connect(bind(&Player::video, this, _1, _2));
- BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
+ for (auto i: playlist()->content()) {
if (!i->paths_valid ()) {
continue;
}
- shared_ptr<Decoder> decoder = decoder_factory (i, _film->log(), _fast);
- FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
-
- if (!decoder) {
- /* Not something that we can decode; e.g. Atmos content */
+ if (_ignore_video && _ignore_audio && i->text.empty()) {
+ /* We're only interested in text and this content has none */
continue;
}
+ shared_ptr<Decoder> old_decoder;
+ for (auto j: old_pieces) {
+ if (j->content == i) {
+ old_decoder = j->decoder;
+ break;
+ }
+ }
+
+ auto decoder = decoder_factory (_film, i, _fast, _tolerant, old_decoder);
+ DCPOMATIC_ASSERT (decoder);
+
+ FrameRateChange frc (_film, i);
+
if (decoder->video && _ignore_video) {
decoder->video->set_ignore (true);
}
- if (decoder->subtitle && _ignore_subtitle) {
- decoder->subtitle->set_ignore (true);
+ if (decoder->audio && _ignore_audio) {
+ decoder->audio->set_ignore (true);
}
- shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
+ if (_ignore_text) {
+ for (auto i: decoder->text) {
+ i->set_ignore (true);
+ }
+ }
+
+ auto dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
if (dcp) {
dcp->set_decode_referenced (_play_referenced);
if (_play_referenced) {
}
}
- shared_ptr<Piece> piece (new Piece (i, decoder, frc));
+ auto piece = make_shared<Piece>(i, decoder, frc);
_pieces.push_back (piece);
if (decoder->video) {
- if (i->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT || i->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) {
+ if (i->video->frame_type() == VideoFrameType::THREE_D_LEFT || i->video->frame_type() == VideoFrameType::THREE_D_RIGHT) {
/* We need a Shuffler to cope with 3D L/R video data arriving out of sequence */
- decoder->video->Data.connect (bind (&Shuffler::video, _shuffler, weak_ptr<Piece>(piece), _1));
+ decoder->video->Data.connect (bind(&Shuffler::video, _shuffler, weak_ptr<Piece>(piece), _1));
} else {
- decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece>(piece), _1));
+ decoder->video->Data.connect (bind(&Player::video, this, weak_ptr<Piece>(piece), _1));
}
}
decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr<Piece> (piece), _1, _2));
}
- if (decoder->subtitle) {
- decoder->subtitle->ImageStart.connect (bind (&Player::image_subtitle_start, this, weak_ptr<Piece> (piece), _1));
- decoder->subtitle->TextStart.connect (bind (&Player::text_subtitle_start, this, weak_ptr<Piece> (piece), _1));
- decoder->subtitle->Stop.connect (bind (&Player::subtitle_stop, this, weak_ptr<Piece> (piece), _1));
+ auto j = decoder->text.begin();
+
+ while (j != decoder->text.end()) {
+ (*j)->BitmapStart.connect (
+ bind(&Player::bitmap_text_start, this, weak_ptr<Piece>(piece), weak_ptr<const TextContent>((*j)->content()), _1)
+ );
+ (*j)->PlainStart.connect (
+ bind(&Player::plain_text_start, this, weak_ptr<Piece>(piece), weak_ptr<const TextContent>((*j)->content()), _1)
+ );
+ (*j)->Stop.connect (
+ bind(&Player::subtitle_stop, this, weak_ptr<Piece>(piece), weak_ptr<const TextContent>((*j)->content()), _1)
+ );
+
+ ++j;
+ }
+
+ if (decoder->atmos) {
+ decoder->atmos->Data.connect (bind(&Player::atmos, this, weak_ptr<Piece>(piece), _1));
}
}
_stream_states.clear ();
- BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+ for (auto i: _pieces) {
if (i->content->audio) {
- BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams()) {
+ for (auto j: i->content->audio->streams()) {
_stream_states[j] = StreamState (i, i->content->position ());
}
}
}
- _black = Empty (_film->content(), _film->length(), bind(&Content::video, _1));
- _silent = Empty (_film->content(), _film->length(), bind(&Content::audio, _1));
+ _black = Empty (_film, playlist(), bind(&have_video, _1), _playback_length);
+ _silent = Empty (_film, playlist(), bind(&have_audio, _1), _playback_length);
_last_video_time = DCPTime ();
- _last_video_eyes = EYES_BOTH;
+ _last_video_eyes = Eyes::BOTH;
_last_audio_time = DCPTime ();
- _have_valid_pieces = true;
}
void
-Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
+Player::playlist_content_change (ChangeType type, int property, bool frequent)
{
- shared_ptr<Content> c = w.lock ();
- if (!c) {
- return;
+ if (property == VideoContentProperty::CROP) {
+ if (type == ChangeType::DONE) {
+ dcp::Size const vcs = video_container_size();
+ boost::mutex::scoped_lock lm (_mutex);
+ for (list<pair<shared_ptr<PlayerVideo>, DCPTime> >::const_iterator i = _delay.begin(); i != _delay.end(); ++i) {
+ i->first->reset_metadata (_film, vcs);
+ }
+ }
+ } else {
+ if (type == ChangeType::PENDING) {
+ /* The player content is probably about to change, so we can't carry on
+ until that has happened and we've rebuilt our pieces. Stop pass()
+ and seek() from working until then.
+ */
+ ++_suspended;
+ } else if (type == ChangeType::DONE) {
+ /* A change in our content has gone through. Re-build our pieces. */
+ setup_pieces ();
+ --_suspended;
+ } else if (type == ChangeType::CANCELLED) {
+ --_suspended;
+ }
}
- if (
- property == ContentProperty::POSITION ||
- property == ContentProperty::LENGTH ||
- property == ContentProperty::TRIM_START ||
- property == ContentProperty::TRIM_END ||
- property == ContentProperty::PATH ||
- property == VideoContentProperty::FRAME_TYPE ||
- property == VideoContentProperty::COLOUR_CONVERSION ||
- property == AudioContentProperty::STREAMS ||
- property == DCPContentProperty::NEEDS_ASSETS ||
- property == DCPContentProperty::NEEDS_KDM ||
- property == SubtitleContentProperty::COLOUR ||
- property == SubtitleContentProperty::EFFECT ||
- property == SubtitleContentProperty::EFFECT_COLOUR ||
- property == FFmpegContentProperty::SUBTITLE_STREAM ||
- property == FFmpegContentProperty::FILTERS
- ) {
-
- _have_valid_pieces = false;
- Changed (property, frequent);
-
- } else if (
- property == SubtitleContentProperty::LINE_SPACING ||
- property == SubtitleContentProperty::OUTLINE_WIDTH ||
- property == SubtitleContentProperty::Y_SCALE ||
- property == SubtitleContentProperty::FADE_IN ||
- property == SubtitleContentProperty::FADE_OUT ||
- property == ContentProperty::VIDEO_FRAME_RATE ||
- property == SubtitleContentProperty::USE ||
- property == SubtitleContentProperty::X_OFFSET ||
- property == SubtitleContentProperty::Y_OFFSET ||
- property == SubtitleContentProperty::X_SCALE ||
- property == SubtitleContentProperty::FONTS ||
- property == VideoContentProperty::CROP ||
- property == VideoContentProperty::SCALE ||
- property == VideoContentProperty::FADE_IN ||
- property == VideoContentProperty::FADE_OUT
- ) {
-
- Changed (property, frequent);
- }
+ Change (type, property, frequent);
}
void
Player::set_video_container_size (dcp::Size s)
{
- if (s == _video_container_size) {
- return;
- }
+ Change (ChangeType::PENDING, PlayerProperty::VIDEO_CONTAINER_SIZE, false);
- _video_container_size = s;
+ {
+ boost::mutex::scoped_lock lm (_mutex);
- _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
- _black_image->make_black ();
+ if (s == _video_container_size) {
+ lm.unlock ();
+ Change (ChangeType::CANCELLED, PlayerProperty::VIDEO_CONTAINER_SIZE, false);
+ return;
+ }
- Changed (PlayerProperty::VIDEO_CONTAINER_SIZE, false);
+ _video_container_size = s;
+
+ _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
+ _black_image->make_black ();
+ }
+
+ Change (ChangeType::DONE, PlayerProperty::VIDEO_CONTAINER_SIZE, false);
}
void
-Player::playlist_changed ()
+Player::playlist_change (ChangeType type)
{
- _have_valid_pieces = false;
- Changed (PlayerProperty::PLAYLIST, false);
+ if (type == ChangeType::DONE) {
+ setup_pieces ();
+ }
+ Change (type, PlayerProperty::PLAYLIST, false);
}
void
-Player::film_changed (Film::Property p)
+Player::film_change (ChangeType type, Film::Property p)
{
/* Here we should notice Film properties that affect our output, and
alert listeners that our output now would be different to how it was
*/
if (p == Film::CONTAINER) {
- Changed (PlayerProperty::FILM_CONTAINER, false);
+ Change (type, PlayerProperty::FILM_CONTAINER, false);
} else if (p == Film::VIDEO_FRAME_RATE) {
/* Pieces contain a FrameRateChange which contains the DCP frame rate,
so we need new pieces here.
*/
- _have_valid_pieces = false;
- Changed (PlayerProperty::FILM_VIDEO_FRAME_RATE, false);
+ if (type == ChangeType::DONE) {
+ setup_pieces ();
+ }
+ Change (type, PlayerProperty::FILM_VIDEO_FRAME_RATE, false);
} else if (p == Film::AUDIO_PROCESSOR) {
- if (_film->audio_processor ()) {
+ if (type == ChangeType::DONE && _film->audio_processor ()) {
+ boost::mutex::scoped_lock lm (_mutex);
_audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
}
} else if (p == Film::AUDIO_CHANNELS) {
- _audio_merger.clear ();
- }
-}
-
-list<PositionImage>
-Player::transform_image_subtitles (list<ImageSubtitle> subs) const
-{
- list<PositionImage> all;
-
- for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
- if (!i->image) {
- continue;
+ if (type == ChangeType::DONE) {
+ boost::mutex::scoped_lock lm (_mutex);
+ _audio_merger.clear ();
}
-
- /* We will scale the subtitle up to fit _video_container_size */
- dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
-
- all.push_back (
- PositionImage (
- i->image->scale (
- scaled_size,
- dcp::YUV_TO_RGB_REC601,
- i->image->pixel_format (),
- true,
- _fast
- ),
- Position<int> (
- lrint (_video_container_size.width * i->rectangle.x),
- lrint (_video_container_size.height * i->rectangle.y)
- )
- )
- );
}
-
- return all;
}
shared_ptr<PlayerVideo>
Player::black_player_video_frame (Eyes eyes) const
{
- return shared_ptr<PlayerVideo> (
- new PlayerVideo (
- shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
- Crop (),
- optional<double> (),
- _video_container_size,
- _video_container_size,
- eyes,
- PART_WHOLE,
- PresetColourConversion::all().front().conversion,
- boost::weak_ptr<Content>(),
- boost::optional<Frame>()
- )
+ return std::make_shared<PlayerVideo> (
+ std::make_shared<const RawImageProxy>(_black_image),
+ Crop(),
+ optional<double>(),
+ _video_container_size,
+ _video_container_size,
+ eyes,
+ Part::WHOLE,
+ PresetColourConversion::all().front().conversion,
+ VideoRange::FULL,
+ std::weak_ptr<Content>(),
+ boost::optional<Frame>(),
+ false
);
}
Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
{
DCPTime s = t - piece->content->position ();
- s = min (piece->content->length_after_trim(), s);
+ s = min (piece->content->length_after_trim(_film), s);
s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
/* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
{
/* See comment in dcp_to_content_video */
- DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime(piece->content->trim_start(), piece->frc);
+ auto const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime(piece->content->trim_start(), piece->frc);
return d + piece->content->position();
}
Frame
Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
{
- DCPTime s = t - piece->content->position ();
- s = min (piece->content->length_after_trim(), s);
+ auto s = t - piece->content->position ();
+ s = min (piece->content->length_after_trim(_film), s);
/* See notes in dcp_to_content_video */
return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
}
ContentTime
Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
{
- DCPTime s = t - piece->content->position ();
- s = min (piece->content->length_after_trim(), s);
+ auto s = t - piece->content->position ();
+ s = min (piece->content->length_after_trim(_film), s);
return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
}
DCPTime
Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
{
- return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
+ return max (DCPTime(), DCPTime(t - piece->content->trim_start(), piece->frc) + piece->content->position());
}
-list<shared_ptr<Font> >
+vector<FontData>
Player::get_subtitle_fonts ()
{
- if (!_have_valid_pieces) {
- setup_pieces ();
- }
+ boost::mutex::scoped_lock lm (_mutex);
- list<shared_ptr<Font> > fonts;
- BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
- if (p->content->subtitle) {
- /* XXX: things may go wrong if there are duplicate font IDs
- with different font files.
- */
- list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
- copy (f.begin(), f.end(), back_inserter (fonts));
- }
+ vector<FontData> fonts;
+ for (auto i: _pieces) {
+ /* XXX: things may go wrong if there are duplicate font IDs
+ with different font files.
+ */
+ auto f = i->decoder->fonts ();
+ copy (f.begin(), f.end(), back_inserter(fonts));
}
return fonts;
void
Player::set_ignore_video ()
{
+ boost::mutex::scoped_lock lm (_mutex);
_ignore_video = true;
+ setup_pieces_unlocked ();
}
void
-Player::set_ignore_subtitle ()
+Player::set_ignore_audio ()
{
- _ignore_subtitle = true;
+ boost::mutex::scoped_lock lm (_mutex);
+ _ignore_audio = true;
+ setup_pieces_unlocked ();
}
-/** Set whether or not this player should always burn text subtitles into the image,
- * regardless of the content settings.
- * @param burn true to always burn subtitles, false to obey content settings.
- */
void
-Player::set_always_burn_subtitles (bool burn)
+Player::set_ignore_text ()
{
- _always_burn_subtitles = burn;
+ boost::mutex::scoped_lock lm (_mutex);
+ _ignore_text = true;
+ setup_pieces_unlocked ();
+}
+
+/** Set the player to always burn open texts into the image regardless of the content settings */
+void
+Player::set_always_burn_open_subtitles ()
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ _always_burn_open_subtitles = true;
}
/** Sets up the player to be faster, possibly at the expense of quality */
void
Player::set_fast ()
{
+ boost::mutex::scoped_lock lm (_mutex);
_fast = true;
- _have_valid_pieces = false;
+ setup_pieces_unlocked ();
}
void
Player::set_play_referenced ()
{
+ boost::mutex::scoped_lock lm (_mutex);
_play_referenced = true;
- _have_valid_pieces = false;
+ setup_pieces_unlocked ();
+}
+
+static void
+maybe_add_asset (list<ReferencedReelAsset>& a, shared_ptr<dcp::ReelAsset> r, Frame reel_trim_start, Frame reel_trim_end, DCPTime from, int const ffr)
+{
+ DCPOMATIC_ASSERT (r);
+ r->set_entry_point (r->entry_point().get_value_or(0) + reel_trim_start);
+ r->set_duration (r->actual_duration() - reel_trim_start - reel_trim_end);
+ if (r->actual_duration() > 0) {
+ a.push_back (
+ ReferencedReelAsset(r, DCPTimePeriod(from, from + DCPTime::from_frames(r->actual_duration(), ffr)))
+ );
+ }
}
list<ReferencedReelAsset>
Player::get_reel_assets ()
{
+ /* Does not require a lock on _mutex as it's only called from DCPEncoder */
+
list<ReferencedReelAsset> a;
- BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
+ for (auto i: playlist()->content()) {
shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
if (!j) {
continue;
scoped_ptr<DCPDecoder> decoder;
try {
- decoder.reset (new DCPDecoder (j, _film->log(), false));
+ decoder.reset (new DCPDecoder (_film, j, false, false, shared_ptr<DCPDecoder>()));
} catch (...) {
return a;
}
- int64_t offset = 0;
- BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
+ DCPOMATIC_ASSERT (j->video_frame_rate ());
+ double const cfr = j->video_frame_rate().get();
+ Frame const trim_start = j->trim_start().frames_round (cfr);
+ Frame const trim_end = j->trim_end().frames_round (cfr);
+ int const ffr = _film->video_frame_rate ();
+
+ /* position in the asset from the start */
+ int64_t offset_from_start = 0;
+ /* position in the asset from the end */
+ int64_t offset_from_end = 0;
+ for (auto k: decoder->reels()) {
+ /* Assume that main picture duration is the length of the reel */
+ offset_from_end += k->main_picture()->actual_duration();
+ }
- DCPOMATIC_ASSERT (j->video_frame_rate ());
- double const cfr = j->video_frame_rate().get();
- Frame const trim_start = j->trim_start().frames_round (cfr);
- Frame const trim_end = j->trim_end().frames_round (cfr);
- int const ffr = _film->video_frame_rate ();
+ for (auto k: decoder->reels()) {
- DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
+ /* Assume that main picture duration is the length of the reel */
+ int64_t const reel_duration = k->main_picture()->actual_duration();
+
+ /* See doc/design/trim_reels.svg */
+ Frame const reel_trim_start = min(reel_duration, max(int64_t(0), trim_start - offset_from_start));
+ Frame const reel_trim_end = min(reel_duration, max(int64_t(0), reel_duration - (offset_from_end - trim_end)));
+
+ DCPTime const from = i->position() + DCPTime::from_frames (offset_from_start, _film->video_frame_rate());
if (j->reference_video ()) {
- shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
- DCPOMATIC_ASSERT (ra);
- ra->set_entry_point (ra->entry_point() + trim_start);
- ra->set_duration (ra->duration() - trim_start - trim_end);
- a.push_back (
- ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
- );
+ maybe_add_asset (a, k->main_picture(), reel_trim_start, reel_trim_end, from, ffr);
}
if (j->reference_audio ()) {
- shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
- DCPOMATIC_ASSERT (ra);
- ra->set_entry_point (ra->entry_point() + trim_start);
- ra->set_duration (ra->duration() - trim_start - trim_end);
- a.push_back (
- ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
- );
+ maybe_add_asset (a, k->main_sound(), reel_trim_start, reel_trim_end, from, ffr);
}
- if (j->reference_subtitle ()) {
- shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
- DCPOMATIC_ASSERT (ra);
- ra->set_entry_point (ra->entry_point() + trim_start);
- ra->set_duration (ra->duration() - trim_start - trim_end);
- a.push_back (
- ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
- );
+ if (j->reference_text (TextType::OPEN_SUBTITLE)) {
+ maybe_add_asset (a, k->main_subtitle(), reel_trim_start, reel_trim_end, from, ffr);
}
- /* Assume that main picture duration is the length of the reel */
- offset += k->main_picture()->duration ();
+ if (j->reference_text (TextType::CLOSED_CAPTION)) {
+ for (auto l: k->closed_captions()) {
+ maybe_add_asset (a, l, reel_trim_start, reel_trim_end, from, ffr);
+ }
+ }
+
+ offset_from_start += reel_duration;
+ offset_from_end -= reel_duration;
}
}
bool
Player::pass ()
{
- if (!_have_valid_pieces) {
- setup_pieces ();
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (_suspended) {
+ /* We can't pass in this state */
+ LOG_DEBUG_PLAYER_NC ("Player is suspended");
+ return false;
}
- if (_playlist->length() == DCPTime()) {
- /* Special case of an empty Film; just give one black frame */
- emit_video (black_player_video_frame(EYES_BOTH), DCPTime());
+ if (_playback_length == DCPTime()) {
+ /* Special; just give one black frame */
+ emit_video (black_player_video_frame(Eyes::BOTH), DCPTime());
return true;
}
shared_ptr<Piece> earliest_content;
optional<DCPTime> earliest_time;
- BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+ for (auto i: _pieces) {
if (i->done) {
continue;
}
DCPTime const t = content_time_to_dcp (i, max(i->decoder->position(), i->content->trim_start()));
- if (t > i->content->end()) {
+ if (t > i->content->end(_film)) {
i->done = true;
} else {
- /* Given two choices at the same time, pick the one with a subtitle so we see it before
+ /* Given two choices at the same time, pick the one with texts so we see it before
the video.
*/
- if (!earliest_time || t < *earliest_time || (t == *earliest_time && i->decoder->subtitle)) {
+ if (!earliest_time || t < *earliest_time || (t == *earliest_time && !i->decoder->text.empty())) {
earliest_time = t;
earliest_content = i;
}
which = CONTENT;
}
- if (!_black.done() && (!earliest_time || _black.position() < *earliest_time)) {
+ if (!_black.done() && !_ignore_video && (!earliest_time || _black.position() < *earliest_time)) {
earliest_time = _black.position ();
which = BLACK;
}
- if (!_silent.done() && (!earliest_time || _silent.position() < *earliest_time)) {
+ if (!_silent.done() && !_ignore_audio && (!earliest_time || _silent.position() < *earliest_time)) {
earliest_time = _silent.position ();
which = SILENT;
}
switch (which) {
case CONTENT:
+ {
+ LOG_DEBUG_PLAYER ("Calling pass() on %1", earliest_content->content->path(0));
earliest_content->done = earliest_content->decoder->pass ();
+ shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent>(earliest_content->content);
+ if (dcp && !_play_referenced && dcp->reference_audio()) {
+ /* We are skipping some referenced DCP audio content, so we need to update _last_audio_time
+ to `hide' the fact that no audio was emitted during the referenced DCP (though
+ we need to behave as though it was).
+ */
+ _last_audio_time = dcp->end (_film);
+ }
break;
+ }
case BLACK:
- emit_video (black_player_video_frame(EYES_BOTH), _black.position());
+ LOG_DEBUG_PLAYER ("Emit black for gap at %1", to_string(_black.position()));
+ emit_video (black_player_video_frame(Eyes::BOTH), _black.position());
_black.set_position (_black.position() + one_video_frame());
break;
case SILENT:
{
+ LOG_DEBUG_PLAYER ("Emit silence for gap at %1", to_string(_silent.position()));
DCPTimePeriod period (_silent.period_at_position());
if (_last_audio_time) {
/* Sometimes the thing that happened last finishes fractionally before
- this silence. Bodge the start time of the silence to fix it. I'm
- not sure if this is the right solution --- maybe the last thing should
- be padded `forward' rather than this thing padding `back'.
+ or after this silence. Bodge the start time of the silence to fix it.
+ I think this is nothing to worry about since we will just add or
+ remove a little silence at the end of some content.
*/
- period.from = min(period.from, *_last_audio_time);
+ int64_t const error = labs(period.from.get() - _last_audio_time->get());
+ /* Let's not worry about less than a frame at 24fps */
+ int64_t const too_much_error = DCPTime::from_frames(1, 24).get();
+ if (error >= too_much_error) {
+ _film->log()->log(String::compose("Silence starting before or after last audio by %1", error), LogEntry::TYPE_ERROR);
+ }
+ DCPOMATIC_ASSERT (error < too_much_error);
+ period.from = *_last_audio_time;
}
if (period.duration() > one_video_frame()) {
period.to = period.from + one_video_frame();
/* Work out the time before which the audio is definitely all here. This is the earliest last_push_end of one
of our streams, or the position of the _silent.
*/
- DCPTime pull_to = _film->length ();
+ DCPTime pull_to = _playback_length;
for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
if (!i->second.piece->done && i->second.last_push_end < pull_to) {
pull_to = i->second.last_push_end;
pull_to = _silent.position();
}
+ LOG_DEBUG_PLAYER("Emitting audio up to %1", to_string(pull_to));
list<pair<shared_ptr<AudioBuffers>, DCPTime> > audio = _audio_merger.pull (pull_to);
for (list<pair<shared_ptr<AudioBuffers>, DCPTime> >::iterator i = audio.begin(); i != audio.end(); ++i) {
if (_last_audio_time && i->second < *_last_audio_time) {
return done;
}
+/** @return Open subtitles for the frame at the given time, converted to images */
optional<PositionImage>
-Player::subtitles_for_frame (DCPTime time) const
+Player::open_subtitles_for_frame (DCPTime time) const
{
- list<PositionImage> subtitles;
-
+ list<PositionImage> captions;
int const vfr = _film->video_frame_rate();
- BOOST_FOREACH (PlayerSubtitles i, _active_subtitles.get_burnt (DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_subtitles)) {
+ for (
+ auto j:
+ _active_texts[static_cast<int>(TextType::OPEN_SUBTITLE)].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles)
+ ) {
- /* Image subtitles */
- list<PositionImage> c = transform_image_subtitles (i.image);
- copy (c.begin(), c.end(), back_inserter (subtitles));
+ /* Bitmap subtitles */
+ for (auto i: j.bitmap) {
+ if (!i.image) {
+ continue;
+ }
+
+ /* i.image will already have been scaled to fit _video_container_size */
+ dcp::Size scaled_size (i.rectangle.width * _video_container_size.width, i.rectangle.height * _video_container_size.height);
+
+ captions.push_back (
+ PositionImage (
+ i.image,
+ Position<int> (
+ lrint (_video_container_size.width * i.rectangle.x),
+ lrint (_video_container_size.height * i.rectangle.y)
+ )
+ )
+ );
+ }
- /* Text subtitles (rendered to an image) */
- if (!i.text.empty ()) {
- list<PositionImage> s = render_subtitles (i.text, i.fonts, _video_container_size, time, vfr);
- copy (s.begin(), s.end(), back_inserter (subtitles));
+ /* String subtitles (rendered to an image) */
+ if (!j.string.empty ()) {
+ list<PositionImage> s = render_text (j.string, j.fonts, _video_container_size, time, vfr);
+ copy (s.begin(), s.end(), back_inserter (captions));
}
}
- if (subtitles.empty ()) {
+ if (captions.empty ()) {
return optional<PositionImage> ();
}
- return merge (subtitles);
+ return merge (captions);
}
void
return;
}
- FrameRateChange frc(piece->content->active_video_frame_rate(), _film->video_frame_rate());
+ if (!piece->content->video->use()) {
+ return;
+ }
+
+ FrameRateChange frc (_film, piece->content);
if (frc.skip && (video.frame % 2) == 1) {
return;
}
/* Time of the first frame we will emit */
DCPTime const time = content_video_to_dcp (piece, video.frame);
+ LOG_DEBUG_PLAYER("Received video frame %1 at %2", video.frame, to_string(time));
/* Discard if it's before the content's period or the last accurate seek. We can't discard
if it's after the content's period here as in that case we still need to fill any gap between
/* Fill gaps that we discover now that we have some video which needs to be emitted.
This is where we need to fill to.
*/
- DCPTime fill_to = min (time, piece->content->end());
+ DCPTime fill_to = min (time, piece->content->end(_film));
if (_last_video_time) {
DCPTime fill_from = max (*_last_video_time, piece->content->position());
- LastVideoMap::const_iterator last = _last_video.find (wp);
- if (_film->three_d()) {
- DCPTime j = fill_from;
- Eyes eyes = _last_video_eyes.get_value_or(EYES_LEFT);
- if (eyes == EYES_BOTH) {
- eyes = EYES_LEFT;
- }
- while (j < fill_to || eyes != video.eyes) {
- if (last != _last_video.end()) {
- shared_ptr<PlayerVideo> copy = last->second->shallow_copy();
- copy->set_eyes (eyes);
- emit_video (copy, j);
- } else {
- emit_video (black_player_video_frame(eyes), j);
+
+ /* Fill if we have more than half a frame to do */
+ if ((fill_to - fill_from) > one_video_frame() / 2) {
+ LastVideoMap::const_iterator last = _last_video.find (wp);
+ if (_film->three_d()) {
+ Eyes fill_to_eyes = video.eyes;
+ if (fill_to_eyes == Eyes::BOTH) {
+ fill_to_eyes = Eyes::LEFT;
}
- if (eyes == EYES_RIGHT) {
- j += one_video_frame();
+ if (fill_to == piece->content->end(_film)) {
+ /* Don't fill after the end of the content */
+ fill_to_eyes = Eyes::LEFT;
}
- eyes = increment_eyes (eyes);
- }
- } else {
- for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) {
- if (last != _last_video.end()) {
- emit_video (last->second, j);
- } else {
- emit_video (black_player_video_frame(EYES_BOTH), j);
+ DCPTime j = fill_from;
+ Eyes eyes = _last_video_eyes.get_value_or(Eyes::LEFT);
+ if (eyes == Eyes::BOTH) {
+ eyes = Eyes::LEFT;
+ }
+ while (j < fill_to || eyes != fill_to_eyes) {
+ if (last != _last_video.end()) {
+ LOG_DEBUG_PLAYER("Fill using last video at %1 in 3D mode", to_string(j));
+ shared_ptr<PlayerVideo> copy = last->second->shallow_copy();
+ copy->set_eyes (eyes);
+ emit_video (copy, j);
+ } else {
+ LOG_DEBUG_PLAYER("Fill using black at %1 in 3D mode", to_string(j));
+ emit_video (black_player_video_frame(eyes), j);
+ }
+ if (eyes == Eyes::RIGHT) {
+ j += one_video_frame();
+ }
+ eyes = increment_eyes (eyes);
+ }
+ } else {
+ for (DCPTime j = fill_from; j < fill_to; j += one_video_frame()) {
+ if (last != _last_video.end()) {
+ emit_video (last->second, j);
+ } else {
+ emit_video (black_player_video_frame(Eyes::BOTH), j);
+ }
}
}
}
new PlayerVideo (
video.image,
piece->content->video->crop (),
- piece->content->video->fade (video.frame),
- piece->content->video->scale().size (
- piece->content->video, _video_container_size, _film->frame_size ()
- ),
+ piece->content->video->fade (_film, video.frame),
+ scale_for_display(piece->content->video->scaled_size(_film->frame_size()), _video_container_size, _film->frame_size()),
_video_container_size,
video.eyes,
video.part,
piece->content->video->colour_conversion(),
+ piece->content->video->range(),
piece->content,
- video.frame
+ video.frame,
+ false
)
);
DCPTime t = time;
for (int i = 0; i < frc.repeat; ++i) {
- if (t < piece->content->end()) {
+ if (t < piece->content->end(_film)) {
emit_video (_last_video[wp], t);
}
t += one_video_frame ();
shared_ptr<AudioContent> content = piece->content->audio;
DCPOMATIC_ASSERT (content);
+ int const rfr = content->resampled_frame_rate (_film);
+
/* Compute time in the DCP */
DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame);
+ LOG_DEBUG_PLAYER("Received audio frame %1 at %2", content_audio.frame, to_string(time));
+
/* And the end of this block in the DCP */
- DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), content->resampled_frame_rate());
+ DCPTime end = time + DCPTime::from_frames(content_audio.audio->frames(), rfr);
/* Remove anything that comes before the start or after the end of the content */
if (time < piece->content->position()) {
}
content_audio.audio = cut.first;
time = cut.second;
- } else if (time > piece->content->end()) {
+ } else if (time > piece->content->end(_film)) {
/* Discard it all */
return;
- } else if (end > piece->content->end()) {
- Frame const remaining_frames = DCPTime(piece->content->end() - time).frames_round(_film->audio_frame_rate());
+ } else if (end > piece->content->end(_film)) {
+ Frame const remaining_frames = DCPTime(piece->content->end(_film) - time).frames_round(rfr);
if (remaining_frames == 0) {
return;
}
- shared_ptr<AudioBuffers> cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames));
- cut->copy_from (content_audio.audio.get(), remaining_frames, 0, 0);
- content_audio.audio = cut;
+ content_audio.audio.reset (new AudioBuffers(content_audio.audio, remaining_frames, 0));
}
DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
}
void
-Player::image_subtitle_start (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
+Player::bitmap_text_start (weak_ptr<Piece> wp, weak_ptr<const TextContent> wc, ContentBitmapText subtitle)
{
shared_ptr<Piece> piece = wp.lock ();
- if (!piece) {
+ shared_ptr<const TextContent> text = wc.lock ();
+ if (!piece || !text) {
return;
}
/* Apply content's subtitle offsets */
- subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
- subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
+ subtitle.sub.rectangle.x += text->x_offset ();
+ subtitle.sub.rectangle.y += text->y_offset ();
/* Apply a corrective translation to keep the subtitle centred after the scale that is coming up */
- subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * ((piece->content->subtitle->x_scale() - 1) / 2);
- subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * ((piece->content->subtitle->y_scale() - 1) / 2);
+ subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * ((text->x_scale() - 1) / 2);
+ subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * ((text->y_scale() - 1) / 2);
/* Apply content's subtitle scale */
- subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
- subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
+ subtitle.sub.rectangle.width *= text->x_scale ();
+ subtitle.sub.rectangle.height *= text->y_scale ();
+
+ PlayerText ps;
+ shared_ptr<Image> image = subtitle.sub.image;
+
+ /* We will scale the subtitle up to fit _video_container_size */
+ int const width = subtitle.sub.rectangle.width * _video_container_size.width;
+ int const height = subtitle.sub.rectangle.height * _video_container_size.height;
+ if (width == 0 || height == 0) {
+ return;
+ }
- PlayerSubtitles ps;
- ps.image.push_back (subtitle.sub);
+ dcp::Size scaled_size (width, height);
+ ps.bitmap.push_back (BitmapText(image->scale(scaled_size, dcp::YUVToRGB::REC601, image->pixel_format(), true, _fast), subtitle.sub.rectangle));
DCPTime from (content_time_to_dcp (piece, subtitle.from()));
- _active_subtitles.add_from (wp, ps, from);
+ _active_texts[static_cast<int>(text->type())].add_from (wc, ps, from);
}
void
-Player::text_subtitle_start (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
+Player::plain_text_start (weak_ptr<Piece> wp, weak_ptr<const TextContent> wc, ContentStringText subtitle)
{
shared_ptr<Piece> piece = wp.lock ();
- if (!piece) {
+ shared_ptr<const TextContent> text = wc.lock ();
+ if (!piece || !text) {
return;
}
- PlayerSubtitles ps;
+ PlayerText ps;
DCPTime const from (content_time_to_dcp (piece, subtitle.from()));
- if (from > piece->content->end()) {
+ if (from > piece->content->end(_film)) {
return;
}
- BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
- s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
- s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
- float const xs = piece->content->subtitle->x_scale();
- float const ys = piece->content->subtitle->y_scale();
+ for (auto s: subtitle.subs) {
+ s.set_h_position (s.h_position() + text->x_offset ());
+ s.set_v_position (s.v_position() + text->y_offset ());
+ float const xs = text->x_scale();
+ float const ys = text->y_scale();
float size = s.size();
/* Adjust size to express the common part of the scaling;
}
s.set_in (dcp::Time(from.seconds(), 1000));
- ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
- ps.add_fonts (piece->content->subtitle->fonts ());
+ ps.string.push_back (StringText (s, text->outline_width()));
+ ps.add_fonts (text->fonts ());
}
- _active_subtitles.add_from (wp, ps, from);
+ _active_texts[static_cast<int>(text->type())].add_from (wc, ps, from);
}
void
-Player::subtitle_stop (weak_ptr<Piece> wp, ContentTime to)
+Player::subtitle_stop (weak_ptr<Piece> wp, weak_ptr<const TextContent> wc, ContentTime to)
{
- if (!_active_subtitles.have (wp)) {
+ shared_ptr<const TextContent> text = wc.lock ();
+ if (!text) {
+ return;
+ }
+
+ if (!_active_texts[static_cast<int>(text->type())].have(wc)) {
return;
}
DCPTime const dcp_to = content_time_to_dcp (piece, to);
- if (dcp_to > piece->content->end()) {
+ if (dcp_to > piece->content->end(_film)) {
return;
}
- pair<PlayerSubtitles, DCPTime> from = _active_subtitles.add_to (wp, dcp_to);
+ pair<PlayerText, DCPTime> from = _active_texts[static_cast<int>(text->type())].add_to (wc, dcp_to);
- if (piece->content->subtitle->use() && !_always_burn_subtitles && !piece->content->subtitle->burn()) {
- Subtitle (from.first, DCPTimePeriod (from.second, dcp_to));
+ bool const always = (text->type() == TextType::OPEN_SUBTITLE && _always_burn_open_subtitles);
+ if (text->use() && !always && !text->burn()) {
+ Text (from.first, text->type(), text->dcp_track().get_value_or(DCPTextTrack()), DCPTimePeriod (from.second, dcp_to));
}
}
void
Player::seek (DCPTime time, bool accurate)
{
- if (!_have_valid_pieces) {
- setup_pieces ();
+ boost::mutex::scoped_lock lm (_mutex);
+ LOG_DEBUG_PLAYER("Seek to %1 (%2accurate)", to_string(time), accurate ? "" : "in");
+
+ if (_suspended) {
+ /* We can't seek in this state */
+ return;
}
if (_shuffler) {
}
_audio_merger.clear ();
- _active_subtitles.clear ();
+ for (int i = 0; i < static_cast<int>(TextType::COUNT); ++i) {
+ _active_texts[i].clear ();
+ }
- BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+ for (auto i: _pieces) {
if (time < i->content->position()) {
- /* Before; seek to the start of the content */
- i->decoder->seek (dcp_to_content_time (i, i->content->position()), accurate);
+ /* Before; seek to the start of the content. Even if this request is for an inaccurate seek
+ we must seek this (following) content accurately, otherwise when we come to the end of the current
+ content we may not start right at the beginning of the next, causing a gap (if the next content has
+ been trimmed to a point between keyframes, or something).
+ */
+ i->decoder->seek (dcp_to_content_time (i, i->content->position()), true);
i->done = false;
- } else if (i->content->position() <= time && time < i->content->end()) {
+ } else if (i->content->position() <= time && time < i->content->end(_film)) {
/* During; seek to position */
i->decoder->seek (dcp_to_content_time (i, time), accurate);
i->done = false;
if (accurate) {
_last_video_time = time;
- _last_video_eyes = EYES_LEFT;
+ _last_video_eyes = Eyes::LEFT;
_last_audio_time = time;
} else {
_last_video_time = optional<DCPTime>();
*/
_delay.push_back (make_pair (pv, time));
- if (pv->eyes() == EYES_BOTH || pv->eyes() == EYES_RIGHT) {
+ if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) {
_last_video_time = time + one_video_frame();
}
_last_video_eyes = increment_eyes (pv->eyes());
void
Player::do_emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
{
- if (pv->eyes() == EYES_BOTH || pv->eyes() == EYES_RIGHT) {
- _active_subtitles.clear_before (time);
+ if (pv->eyes() == Eyes::BOTH || pv->eyes() == Eyes::RIGHT) {
+ for (int i = 0; i < static_cast<int>(TextType::COUNT); ++i) {
+ _active_texts[i].clear_before (time);
+ }
}
- optional<PositionImage> subtitles = subtitles_for_frame (time);
+ auto subtitles = open_subtitles_for_frame (time);
if (subtitles) {
- pv->set_subtitle (subtitles.get ());
+ pv->set_text (subtitles.get ());
}
Video (pv, time);
Player::emit_audio (shared_ptr<AudioBuffers> data, DCPTime time)
{
/* Log if the assert below is about to fail */
- if (_last_audio_time && time != *_last_audio_time) {
+ if (_last_audio_time && labs(time.get() - _last_audio_time->get()) > 1) {
_film->log()->log(String::compose("Out-of-sequence emit %1 vs %2", to_string(time), to_string(*_last_audio_time)), LogEntry::TYPE_WARNING);
}
- /* This audio must follow on from the previous */
- DCPOMATIC_ASSERT (!_last_audio_time || time == *_last_audio_time);
- Audio (data, time);
+ /* This audio must follow on from the previous, allowing for half a sample (at 48kHz) leeway */
+ DCPOMATIC_ASSERT (!_last_audio_time || labs(time.get() - _last_audio_time->get()) < 2);
+ Audio (data, time, _film->audio_frame_rate());
_last_audio_time = time + DCPTime::from_frames (data->frames(), _film->audio_frame_rate());
}
if (remaining_frames <= 0) {
return make_pair(shared_ptr<AudioBuffers>(), DCPTime());
}
- shared_ptr<AudioBuffers> cut (new AudioBuffers (audio->channels(), remaining_frames));
- cut->copy_from (audio.get(), remaining_frames, discard_frames, 0);
+ shared_ptr<AudioBuffers> cut (new AudioBuffers(audio, remaining_frames, discard_frames));
return make_pair(cut, time + discard_time);
}
void
Player::set_dcp_decode_reduction (optional<int> reduction)
{
- if (reduction == _dcp_decode_reduction) {
- return;
+ Change (ChangeType::PENDING, PlayerProperty::DCP_DECODE_REDUCTION, false);
+
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (reduction == _dcp_decode_reduction) {
+ lm.unlock ();
+ Change (ChangeType::CANCELLED, PlayerProperty::DCP_DECODE_REDUCTION, false);
+ return;
+ }
+
+ _dcp_decode_reduction = reduction;
+ setup_pieces_unlocked ();
}
- _dcp_decode_reduction = reduction;
- _have_valid_pieces = false;
- Changed (PlayerProperty::DCP_DECODE_REDUCTION, false);
+ Change (ChangeType::DONE, PlayerProperty::DCP_DECODE_REDUCTION, false);
}
-DCPTime
+optional<DCPTime>
Player::content_time_to_dcp (shared_ptr<Content> content, ContentTime t)
{
- if (_have_valid_pieces) {
- setup_pieces ();
- }
+ boost::mutex::scoped_lock lm (_mutex);
- BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
+ for (auto i: _pieces) {
if (i->content == content) {
return content_time_to_dcp (i, t);
}
}
- DCPOMATIC_ASSERT (false);
- return DCPTime ();
+ /* We couldn't find this content; perhaps things are being changed over */
+ return {};
}
+
+
+shared_ptr<const Playlist>
+Player::playlist () const
+{
+ return _playlist ? _playlist : _film->playlist();
+}
+
+
+void
+Player::atmos (weak_ptr<Piece>, ContentAtmos data)
+{
+ Atmos (data.data, DCPTime::from_frames(data.frame, _film->video_frame_rate()), data.metadata);
+}
+