2 Copyright (C) 2013-2017 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
23 #include "audio_buffers.h"
24 #include "content_audio.h"
25 #include "dcp_content.h"
28 #include "raw_image_proxy.h"
31 #include "render_subtitles.h"
33 #include "content_video.h"
34 #include "player_video.h"
35 #include "frame_rate_change.h"
36 #include "audio_processor.h"
38 #include "referenced_reel_asset.h"
39 #include "decoder_factory.h"
41 #include "video_decoder.h"
42 #include "audio_decoder.h"
43 #include "subtitle_content.h"
44 #include "subtitle_decoder.h"
45 #include "ffmpeg_content.h"
46 #include "audio_content.h"
47 #include "content_subtitle.h"
48 #include "dcp_decoder.h"
49 #include "image_decoder.h"
50 #include "resampler.h"
51 #include "compose.hpp"
53 #include <dcp/reel_sound_asset.h>
54 #include <dcp/reel_subtitle_asset.h>
55 #include <dcp/reel_picture_asset.h>
56 #include <boost/foreach.hpp>
63 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
75 using boost::shared_ptr;
76 using boost::weak_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::optional;
79 using boost::scoped_ptr;
81 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
83 , _playlist (playlist)
84 , _have_valid_pieces (false)
85 , _ignore_video (false)
86 , _ignore_audio (false)
87 , _always_burn_subtitles (false)
89 , _play_referenced (false)
90 , _audio_merger (_film->audio_channels(), _film->audio_frame_rate())
92 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
93 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
94 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
95 set_video_container_size (_film->frame_size ());
97 film_changed (Film::AUDIO_PROCESSOR);
101 Player::setup_pieces ()
105 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
107 if (!i->paths_valid ()) {
111 shared_ptr<Decoder> decoder = decoder_factory (i, _film->log());
112 FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
115 /* Not something that we can decode; e.g. Atmos content */
119 if (decoder->video && _ignore_video) {
120 decoder->video->set_ignore ();
123 if (decoder->audio && _ignore_audio) {
124 decoder->audio->set_ignore ();
127 shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
128 if (dcp && _play_referenced) {
129 dcp->set_decode_referenced ();
132 shared_ptr<Piece> piece (new Piece (i, decoder, frc));
133 _pieces.push_back (piece);
135 if (decoder->video) {
136 decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece> (piece), _1));
139 if (decoder->audio) {
140 decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr<Piece> (piece), _1, _2));
143 if (decoder->subtitle) {
144 decoder->subtitle->ImageData.connect (bind (&Player::image_subtitle, this, weak_ptr<Piece> (piece), _1));
145 decoder->subtitle->TextData.connect (bind (&Player::text_subtitle, this, weak_ptr<Piece> (piece), _1));
149 _have_valid_pieces = true;
153 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
155 shared_ptr<Content> c = w.lock ();
161 property == ContentProperty::POSITION ||
162 property == ContentProperty::LENGTH ||
163 property == ContentProperty::TRIM_START ||
164 property == ContentProperty::TRIM_END ||
165 property == ContentProperty::PATH ||
166 property == VideoContentProperty::FRAME_TYPE ||
167 property == DCPContentProperty::NEEDS_ASSETS ||
168 property == DCPContentProperty::NEEDS_KDM ||
169 property == SubtitleContentProperty::COLOUR ||
170 property == SubtitleContentProperty::OUTLINE ||
171 property == SubtitleContentProperty::SHADOW ||
172 property == SubtitleContentProperty::EFFECT_COLOUR ||
173 property == FFmpegContentProperty::SUBTITLE_STREAM ||
174 property == VideoContentProperty::COLOUR_CONVERSION
177 _have_valid_pieces = false;
181 property == SubtitleContentProperty::LINE_SPACING ||
182 property == SubtitleContentProperty::OUTLINE_WIDTH ||
183 property == SubtitleContentProperty::Y_SCALE ||
184 property == SubtitleContentProperty::FADE_IN ||
185 property == SubtitleContentProperty::FADE_OUT ||
186 property == ContentProperty::VIDEO_FRAME_RATE ||
187 property == SubtitleContentProperty::USE ||
188 property == SubtitleContentProperty::X_OFFSET ||
189 property == SubtitleContentProperty::Y_OFFSET ||
190 property == SubtitleContentProperty::X_SCALE ||
191 property == SubtitleContentProperty::FONTS ||
192 property == VideoContentProperty::CROP ||
193 property == VideoContentProperty::SCALE ||
194 property == VideoContentProperty::FADE_IN ||
195 property == VideoContentProperty::FADE_OUT
203 Player::set_video_container_size (dcp::Size s)
205 _video_container_size = s;
207 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
208 _black_image->make_black ();
212 Player::playlist_changed ()
214 _have_valid_pieces = false;
219 Player::film_changed (Film::Property p)
221 /* Here we should notice Film properties that affect our output, and
222 alert listeners that our output now would be different to how it was
223 last time we were run.
226 if (p == Film::CONTAINER) {
228 } else if (p == Film::VIDEO_FRAME_RATE) {
229 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
230 so we need new pieces here.
232 _have_valid_pieces = false;
234 } else if (p == Film::AUDIO_PROCESSOR) {
235 if (_film->audio_processor ()) {
236 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
242 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
244 list<PositionImage> all;
246 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
251 /* We will scale the subtitle up to fit _video_container_size */
252 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
254 /* Then we need a corrective translation, consisting of two parts:
256 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
257 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
259 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
260 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
261 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
263 * Combining these two translations gives these expressions.
270 dcp::YUV_TO_RGB_REC601,
271 i->image->pixel_format (),
276 lrint (_video_container_size.width * i->rectangle.x),
277 lrint (_video_container_size.height * i->rectangle.y)
286 shared_ptr<PlayerVideo>
287 Player::black_player_video_frame () const
289 return shared_ptr<PlayerVideo> (
291 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
294 _video_container_size,
295 _video_container_size,
298 PresetColourConversion::all().front().conversion
304 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
306 DCPTime s = t - piece->content->position ();
307 s = min (piece->content->length_after_trim(), s);
308 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
310 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
311 then convert that ContentTime to frames at the content's rate. However this fails for
312 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
313 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
315 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
317 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
321 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
323 /* See comment in dcp_to_content_video */
324 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
325 return max (DCPTime (), d + piece->content->position ());
329 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
331 DCPTime s = t - piece->content->position ();
332 s = min (piece->content->length_after_trim(), s);
333 /* See notes in dcp_to_content_video */
334 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
338 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
340 /* See comment in dcp_to_content_video */
341 DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start (), piece->frc);
342 return max (DCPTime (), d + piece->content->position ());
346 Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
348 DCPTime s = t - piece->content->position ();
349 s = min (piece->content->length_after_trim(), s);
350 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
354 Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
356 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
359 list<shared_ptr<Font> >
360 Player::get_subtitle_fonts ()
362 if (!_have_valid_pieces) {
366 list<shared_ptr<Font> > fonts;
367 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
368 if (p->content->subtitle) {
369 /* XXX: things may go wrong if there are duplicate font IDs
370 with different font files.
372 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
373 copy (f.begin(), f.end(), back_inserter (fonts));
380 /** Set this player never to produce any video data */
382 Player::set_ignore_video ()
384 _ignore_video = true;
387 /** Set this player never to produce any audio data */
389 Player::set_ignore_audio ()
391 _ignore_audio = true;
394 /** Set whether or not this player should always burn text subtitles into the image,
395 * regardless of the content settings.
396 * @param burn true to always burn subtitles, false to obey content settings.
399 Player::set_always_burn_subtitles (bool burn)
401 _always_burn_subtitles = burn;
408 _have_valid_pieces = false;
412 Player::set_play_referenced ()
414 _play_referenced = true;
415 _have_valid_pieces = false;
418 list<ReferencedReelAsset>
419 Player::get_reel_assets ()
421 list<ReferencedReelAsset> a;
423 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
424 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
429 scoped_ptr<DCPDecoder> decoder;
431 decoder.reset (new DCPDecoder (j, _film->log()));
437 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
439 DCPOMATIC_ASSERT (j->video_frame_rate ());
440 double const cfr = j->video_frame_rate().get();
441 Frame const trim_start = j->trim_start().frames_round (cfr);
442 Frame const trim_end = j->trim_end().frames_round (cfr);
443 int const ffr = _film->video_frame_rate ();
445 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
446 if (j->reference_video ()) {
447 shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
448 DCPOMATIC_ASSERT (ra);
449 ra->set_entry_point (ra->entry_point() + trim_start);
450 ra->set_duration (ra->duration() - trim_start - trim_end);
452 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
456 if (j->reference_audio ()) {
457 shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
458 DCPOMATIC_ASSERT (ra);
459 ra->set_entry_point (ra->entry_point() + trim_start);
460 ra->set_duration (ra->duration() - trim_start - trim_end);
462 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
466 if (j->reference_subtitle ()) {
467 shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
468 DCPOMATIC_ASSERT (ra);
469 ra->set_entry_point (ra->entry_point() + trim_start);
470 ra->set_duration (ra->duration() - trim_start - trim_end);
472 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
476 /* Assume that main picture duration is the length of the reel */
477 offset += k->main_picture()->duration ();
484 list<shared_ptr<Piece> >
485 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
487 if (!_have_valid_pieces) {
491 list<shared_ptr<Piece> > overlaps;
492 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
493 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
494 overlaps.push_back (i);
504 if (!_have_valid_pieces) {
508 shared_ptr<Piece> earliest;
509 DCPTime earliest_content;
511 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
513 DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
514 if (!earliest || t < earliest_content) {
515 earliest_content = t;
522 /* No more content; fill up to the length of our playlist with silent black */
524 DCPTime const length = _playlist->length ();
526 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
529 from = _last_time.get() + frame;
531 for (DCPTime i = from; i < length; i += frame) {
532 Video (black_player_video_frame (), i);
535 DCPTime t = _last_audio_time;
537 DCPTime block = min (DCPTime::from_seconds (0.5), length - t);
538 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
539 silence->make_silent ();
547 earliest->done = earliest->decoder->pass ();
549 /* Emit any audio that is ready */
551 DCPTime pull_from = _playlist->length ();
552 for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
553 if (!i->second.piece->done && i->second.last_push_end < pull_from) {
554 pull_from = i->second.last_push_end;
558 pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (pull_from);
559 if (audio.first->frames() > 0) {
560 DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
561 DCPTime t = _last_audio_time;
562 while (t < audio.second) {
563 /* Silence up to the time of this new audio */
564 DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
565 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
566 silence->make_silent ();
571 Audio (audio.first, audio.second);
572 _last_audio_time = audio.second + DCPTime::from_frames(audio.first->frames(), _film->audio_frame_rate());
579 Player::video (weak_ptr<Piece> wp, ContentVideo video)
581 shared_ptr<Piece> piece = wp.lock ();
586 /* Time and period of the frame we will emit */
587 DCPTime const time = content_video_to_dcp (piece, video.frame);
588 DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
590 /* Discard if it's outside the content's period */
591 if (time < piece->content->position() || time >= piece->content->end()) {
595 /* Get any subtitles */
597 optional<PositionImage> subtitles;
599 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
601 if (!i->second.overlap (period)) {
605 list<PositionImage> sub_images;
607 /* Image subtitles */
608 list<PositionImage> c = transform_image_subtitles (i->first.image);
609 copy (c.begin(), c.end(), back_inserter (sub_images));
611 /* Text subtitles (rendered to an image) */
612 if (!i->first.text.empty ()) {
613 list<PositionImage> s = render_subtitles (i->first.text, i->first.fonts, _video_container_size, time);
614 copy (s.begin (), s.end (), back_inserter (sub_images));
617 if (!sub_images.empty ()) {
618 subtitles = merge (sub_images);
625 /* XXX: this may not work for 3D */
626 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
627 for (DCPTime i = _last_time.get() + frame; i < time; i += frame) {
628 if (_playlist->video_content_at(i) && _last_video) {
629 Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
631 Video (black_player_video_frame (), i);
639 piece->content->video->crop (),
640 piece->content->video->fade (video.frame),
641 piece->content->video->scale().size (
642 piece->content->video, _video_container_size, _film->frame_size ()
644 _video_container_size,
647 piece->content->video->colour_conversion ()
652 _last_video->set_subtitle (subtitles.get ());
657 Video (_last_video, *_last_time);
659 /* Discard any subtitles we no longer need */
661 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator i = _subtitles.begin (); i != _subtitles.end(); ) {
662 list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator tmp = i;
665 if (i->second.to < time) {
666 _subtitles.erase (i);
674 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
676 shared_ptr<Piece> piece = wp.lock ();
681 shared_ptr<AudioContent> content = piece->content->audio;
682 DCPOMATIC_ASSERT (content);
685 if (content->gain() != 0) {
686 shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
687 gain->apply_gain (content->gain ());
688 content_audio.audio = gain;
692 if (stream->frame_rate() != content->resampled_frame_rate()) {
693 shared_ptr<Resampler> r = resampler (content, stream, true);
694 pair<shared_ptr<const AudioBuffers>, Frame> ro = r->run (content_audio.audio, content_audio.frame);
695 content_audio.audio = ro.first;
696 content_audio.frame = ro.second;
699 /* XXX: end-trimming used to be checked here */
701 /* Compute time in the DCP */
702 DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
704 /* Remove anything that comes before the start of the content */
705 if (time < piece->content->position()) {
706 DCPTime const discard_time = piece->content->position() - time;
707 Frame discard_frames = discard_time.frames_round(_film->audio_frame_rate());
708 Frame remaining_frames = content_audio.audio->frames() - discard_frames;
709 if (remaining_frames <= 0) {
710 /* This audio is entirely discarded */
713 shared_ptr<AudioBuffers> cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames));
714 cut->copy_from (content_audio.audio.get(), remaining_frames, discard_frames, 0);
715 content_audio.audio = cut;
716 time += discard_time;
720 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), content_audio.audio->frames()));
721 dcp_mapped->make_silent ();
723 AudioMapping map = stream->mapping ();
724 for (int i = 0; i < map.input_channels(); ++i) {
725 for (int j = 0; j < dcp_mapped->channels(); ++j) {
726 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
727 dcp_mapped->accumulate_channel (
728 content_audio.audio.get(),
730 static_cast<dcp::Channel> (j),
731 map.get (i, static_cast<dcp::Channel> (j))
737 content_audio.audio = dcp_mapped;
739 if (_audio_processor) {
740 content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
743 _audio_merger.push (content_audio.audio, time);
745 if (_stream_states.find (stream) == _stream_states.end ()) {
746 _stream_states[stream] = StreamState (piece, time);
748 _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
753 Player::image_subtitle (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
755 shared_ptr<Piece> piece = wp.lock ();
760 /* Apply content's subtitle offsets */
761 subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
762 subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
764 /* Apply content's subtitle scale */
765 subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
766 subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
768 /* Apply a corrective translation to keep the subtitle centred after that scale */
769 subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * (piece->content->subtitle->x_scale() - 1);
770 subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * (piece->content->subtitle->y_scale() - 1);
773 ps.image.push_back (subtitle.sub);
774 DCPTimePeriod period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
776 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
777 _subtitles.push_back (make_pair (ps, period));
779 Subtitle (ps, period);
784 Player::text_subtitle (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
786 shared_ptr<Piece> piece = wp.lock ();
792 DCPTimePeriod const period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
794 BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
795 s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
796 s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
797 float const xs = piece->content->subtitle->x_scale();
798 float const ys = piece->content->subtitle->y_scale();
799 float size = s.size();
801 /* Adjust size to express the common part of the scaling;
802 e.g. if xs = ys = 0.5 we scale size by 2.
804 if (xs > 1e-5 && ys > 1e-5) {
805 size *= 1 / min (1 / xs, 1 / ys);
809 /* Then express aspect ratio changes */
810 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
811 s.set_aspect_adjust (xs / ys);
814 s.set_in (dcp::Time(period.from.seconds(), 1000));
815 s.set_out (dcp::Time(period.to.seconds(), 1000));
816 ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
817 ps.add_fonts (piece->content->subtitle->fonts ());
820 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
821 _subtitles.push_back (make_pair (ps, period));
823 Subtitle (ps, period);
828 Player::seek (DCPTime time, bool accurate)
830 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
831 if (i->content->position() <= time && time < i->content->end()) {
832 i->decoder->seek (dcp_to_content_time (i, time), accurate);
838 _last_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
840 _last_time = optional<DCPTime> ();
844 shared_ptr<Resampler>
845 Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create)
847 ResamplerMap::const_iterator i = _resamplers.find (make_pair (content, stream));
848 if (i != _resamplers.end ()) {
853 return shared_ptr<Resampler> ();
857 "Creating new resampler from %1 to %2 with %3 channels",
858 stream->frame_rate(),
859 content->resampled_frame_rate(),
863 shared_ptr<Resampler> r (
864 new Resampler (stream->frame_rate(), content->resampled_frame_rate(), stream->channels())
867 _resamplers[make_pair(content, stream)] = r;