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 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
150 if (i->content->audio) {
151 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams()) {
152 _stream_states[j] = StreamState (i, i->content->position ());
157 _have_valid_pieces = true;
161 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
163 shared_ptr<Content> c = w.lock ();
169 property == ContentProperty::POSITION ||
170 property == ContentProperty::LENGTH ||
171 property == ContentProperty::TRIM_START ||
172 property == ContentProperty::TRIM_END ||
173 property == ContentProperty::PATH ||
174 property == VideoContentProperty::FRAME_TYPE ||
175 property == DCPContentProperty::NEEDS_ASSETS ||
176 property == DCPContentProperty::NEEDS_KDM ||
177 property == SubtitleContentProperty::COLOUR ||
178 property == SubtitleContentProperty::OUTLINE ||
179 property == SubtitleContentProperty::SHADOW ||
180 property == SubtitleContentProperty::EFFECT_COLOUR ||
181 property == FFmpegContentProperty::SUBTITLE_STREAM ||
182 property == VideoContentProperty::COLOUR_CONVERSION
185 _have_valid_pieces = false;
189 property == SubtitleContentProperty::LINE_SPACING ||
190 property == SubtitleContentProperty::OUTLINE_WIDTH ||
191 property == SubtitleContentProperty::Y_SCALE ||
192 property == SubtitleContentProperty::FADE_IN ||
193 property == SubtitleContentProperty::FADE_OUT ||
194 property == ContentProperty::VIDEO_FRAME_RATE ||
195 property == SubtitleContentProperty::USE ||
196 property == SubtitleContentProperty::X_OFFSET ||
197 property == SubtitleContentProperty::Y_OFFSET ||
198 property == SubtitleContentProperty::X_SCALE ||
199 property == SubtitleContentProperty::FONTS ||
200 property == VideoContentProperty::CROP ||
201 property == VideoContentProperty::SCALE ||
202 property == VideoContentProperty::FADE_IN ||
203 property == VideoContentProperty::FADE_OUT
211 Player::set_video_container_size (dcp::Size s)
213 _video_container_size = s;
215 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
216 _black_image->make_black ();
220 Player::playlist_changed ()
222 _have_valid_pieces = false;
227 Player::film_changed (Film::Property p)
229 /* Here we should notice Film properties that affect our output, and
230 alert listeners that our output now would be different to how it was
231 last time we were run.
234 if (p == Film::CONTAINER) {
236 } else if (p == Film::VIDEO_FRAME_RATE) {
237 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
238 so we need new pieces here.
240 _have_valid_pieces = false;
242 } else if (p == Film::AUDIO_PROCESSOR) {
243 if (_film->audio_processor ()) {
244 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
250 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
252 list<PositionImage> all;
254 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
259 /* We will scale the subtitle up to fit _video_container_size */
260 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
262 /* Then we need a corrective translation, consisting of two parts:
264 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
265 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
267 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
268 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
269 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
271 * Combining these two translations gives these expressions.
278 dcp::YUV_TO_RGB_REC601,
279 i->image->pixel_format (),
284 lrint (_video_container_size.width * i->rectangle.x),
285 lrint (_video_container_size.height * i->rectangle.y)
294 shared_ptr<PlayerVideo>
295 Player::black_player_video_frame () const
297 return shared_ptr<PlayerVideo> (
299 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
302 _video_container_size,
303 _video_container_size,
306 PresetColourConversion::all().front().conversion
312 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
314 DCPTime s = t - piece->content->position ();
315 s = min (piece->content->length_after_trim(), s);
316 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
318 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
319 then convert that ContentTime to frames at the content's rate. However this fails for
320 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
321 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
323 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
325 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
329 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
331 /* See comment in dcp_to_content_video */
332 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
333 return max (DCPTime (), d + piece->content->position ());
337 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
339 DCPTime s = t - piece->content->position ();
340 s = min (piece->content->length_after_trim(), s);
341 /* See notes in dcp_to_content_video */
342 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
346 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
348 /* See comment in dcp_to_content_video */
349 DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start (), piece->frc);
350 return max (DCPTime (), d + piece->content->position ());
354 Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
356 DCPTime s = t - piece->content->position ();
357 s = min (piece->content->length_after_trim(), s);
358 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
362 Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
364 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
367 list<shared_ptr<Font> >
368 Player::get_subtitle_fonts ()
370 if (!_have_valid_pieces) {
374 list<shared_ptr<Font> > fonts;
375 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
376 if (p->content->subtitle) {
377 /* XXX: things may go wrong if there are duplicate font IDs
378 with different font files.
380 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
381 copy (f.begin(), f.end(), back_inserter (fonts));
388 /** Set this player never to produce any video data */
390 Player::set_ignore_video ()
392 _ignore_video = true;
395 /** Set this player never to produce any audio data */
397 Player::set_ignore_audio ()
399 _ignore_audio = true;
402 /** Set whether or not this player should always burn text subtitles into the image,
403 * regardless of the content settings.
404 * @param burn true to always burn subtitles, false to obey content settings.
407 Player::set_always_burn_subtitles (bool burn)
409 _always_burn_subtitles = burn;
416 _have_valid_pieces = false;
420 Player::set_play_referenced ()
422 _play_referenced = true;
423 _have_valid_pieces = false;
426 list<ReferencedReelAsset>
427 Player::get_reel_assets ()
429 list<ReferencedReelAsset> a;
431 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
432 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
437 scoped_ptr<DCPDecoder> decoder;
439 decoder.reset (new DCPDecoder (j, _film->log()));
445 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
447 DCPOMATIC_ASSERT (j->video_frame_rate ());
448 double const cfr = j->video_frame_rate().get();
449 Frame const trim_start = j->trim_start().frames_round (cfr);
450 Frame const trim_end = j->trim_end().frames_round (cfr);
451 int const ffr = _film->video_frame_rate ();
453 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
454 if (j->reference_video ()) {
455 shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
456 DCPOMATIC_ASSERT (ra);
457 ra->set_entry_point (ra->entry_point() + trim_start);
458 ra->set_duration (ra->duration() - trim_start - trim_end);
460 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
464 if (j->reference_audio ()) {
465 shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
466 DCPOMATIC_ASSERT (ra);
467 ra->set_entry_point (ra->entry_point() + trim_start);
468 ra->set_duration (ra->duration() - trim_start - trim_end);
470 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
474 if (j->reference_subtitle ()) {
475 shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
476 DCPOMATIC_ASSERT (ra);
477 ra->set_entry_point (ra->entry_point() + trim_start);
478 ra->set_duration (ra->duration() - trim_start - trim_end);
480 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
484 /* Assume that main picture duration is the length of the reel */
485 offset += k->main_picture()->duration ();
492 list<shared_ptr<Piece> >
493 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
495 if (!_have_valid_pieces) {
499 list<shared_ptr<Piece> > overlaps;
500 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
501 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
502 overlaps.push_back (i);
512 if (!_have_valid_pieces) {
516 shared_ptr<Piece> earliest;
517 DCPTime earliest_content;
519 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
521 DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
522 if (!earliest || t < earliest_content) {
523 earliest_content = t;
530 /* No more content; fill up to the length of our playlist with silent black */
532 DCPTime const length = _playlist->length ();
534 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
537 from = _last_time.get() + frame;
539 for (DCPTime i = from; i < length; i += frame) {
540 Video (black_player_video_frame (), i);
543 DCPTime t = _last_audio_time;
545 DCPTime block = min (DCPTime::from_seconds (0.5), length - t);
546 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
547 silence->make_silent ();
555 earliest->done = earliest->decoder->pass ();
557 /* Emit any audio that is ready */
559 DCPTime pull_from = _playlist->length ();
560 for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
561 if (!i->second.piece->done && i->second.last_push_end < pull_from) {
562 pull_from = i->second.last_push_end;
566 // cout << "PULL " << to_string(pull_from) << "\n";
567 pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (pull_from);
568 if (audio.first->frames() > 0) {
569 DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
570 DCPTime t = _last_audio_time;
571 while (t < audio.second) {
572 /* Silence up to the time of this new audio */
573 DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
574 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
575 silence->make_silent ();
580 Audio (audio.first, audio.second);
581 _last_audio_time = audio.second + DCPTime::from_frames(audio.first->frames(), _film->audio_frame_rate());
588 Player::video (weak_ptr<Piece> wp, ContentVideo video)
590 shared_ptr<Piece> piece = wp.lock ();
595 /* Time and period of the frame we will emit */
596 DCPTime const time = content_video_to_dcp (piece, video.frame);
597 DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
599 /* Discard if it's outside the content's period */
600 if (time < piece->content->position() || time >= piece->content->end()) {
604 /* Get any subtitles */
606 optional<PositionImage> subtitles;
608 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
610 if (!i->second.overlap (period)) {
614 list<PositionImage> sub_images;
616 /* Image subtitles */
617 list<PositionImage> c = transform_image_subtitles (i->first.image);
618 copy (c.begin(), c.end(), back_inserter (sub_images));
620 /* Text subtitles (rendered to an image) */
621 if (!i->first.text.empty ()) {
622 list<PositionImage> s = render_subtitles (i->first.text, i->first.fonts, _video_container_size, time);
623 copy (s.begin (), s.end (), back_inserter (sub_images));
626 if (!sub_images.empty ()) {
627 subtitles = merge (sub_images);
634 /* XXX: this may not work for 3D */
635 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
636 for (DCPTime i = _last_time.get() + frame; i < time; i += frame) {
637 if (_playlist->video_content_at(i) && _last_video) {
638 Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
640 Video (black_player_video_frame (), i);
648 piece->content->video->crop (),
649 piece->content->video->fade (video.frame),
650 piece->content->video->scale().size (
651 piece->content->video, _video_container_size, _film->frame_size ()
653 _video_container_size,
656 piece->content->video->colour_conversion ()
661 _last_video->set_subtitle (subtitles.get ());
666 Video (_last_video, *_last_time);
668 /* Discard any subtitles we no longer need */
670 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator i = _subtitles.begin (); i != _subtitles.end(); ) {
671 list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator tmp = i;
674 if (i->second.to < time) {
675 _subtitles.erase (i);
683 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
685 shared_ptr<Piece> piece = wp.lock ();
690 shared_ptr<AudioContent> content = piece->content->audio;
691 DCPOMATIC_ASSERT (content);
694 if (content->gain() != 0) {
695 shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
696 gain->apply_gain (content->gain ());
697 content_audio.audio = gain;
701 if (stream->frame_rate() != content->resampled_frame_rate()) {
702 shared_ptr<Resampler> r = resampler (content, stream, true);
703 pair<shared_ptr<const AudioBuffers>, Frame> ro = r->run (content_audio.audio, content_audio.frame);
704 content_audio.audio = ro.first;
705 content_audio.frame = ro.second;
708 /* XXX: end-trimming used to be checked here */
710 /* Compute time in the DCP */
711 DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
713 /* Remove anything that comes before the start of the content */
714 if (time < piece->content->position()) {
715 DCPTime const discard_time = piece->content->position() - time;
716 Frame discard_frames = discard_time.frames_round(_film->audio_frame_rate());
717 Frame remaining_frames = content_audio.audio->frames() - discard_frames;
718 if (remaining_frames <= 0) {
719 /* This audio is entirely discarded */
722 shared_ptr<AudioBuffers> cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames));
723 cut->copy_from (content_audio.audio.get(), remaining_frames, discard_frames, 0);
724 content_audio.audio = cut;
725 time += discard_time;
729 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), content_audio.audio->frames()));
730 dcp_mapped->make_silent ();
732 AudioMapping map = stream->mapping ();
733 for (int i = 0; i < map.input_channels(); ++i) {
734 for (int j = 0; j < dcp_mapped->channels(); ++j) {
735 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
736 dcp_mapped->accumulate_channel (
737 content_audio.audio.get(),
739 static_cast<dcp::Channel> (j),
740 map.get (i, static_cast<dcp::Channel> (j))
746 content_audio.audio = dcp_mapped;
748 if (_audio_processor) {
749 content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
752 // cout << "PUSH " << content_audio.audio->frames() << " @ " << to_string(time) << "\n";
753 _audio_merger.push (content_audio.audio, time);
755 DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
756 _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
760 Player::image_subtitle (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
762 shared_ptr<Piece> piece = wp.lock ();
767 /* Apply content's subtitle offsets */
768 subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
769 subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
771 /* Apply content's subtitle scale */
772 subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
773 subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
775 /* Apply a corrective translation to keep the subtitle centred after that scale */
776 subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * (piece->content->subtitle->x_scale() - 1);
777 subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * (piece->content->subtitle->y_scale() - 1);
780 ps.image.push_back (subtitle.sub);
781 DCPTimePeriod period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
783 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
784 _subtitles.push_back (make_pair (ps, period));
786 Subtitle (ps, period);
791 Player::text_subtitle (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
793 shared_ptr<Piece> piece = wp.lock ();
799 DCPTimePeriod const period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
801 BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
802 s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
803 s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
804 float const xs = piece->content->subtitle->x_scale();
805 float const ys = piece->content->subtitle->y_scale();
806 float size = s.size();
808 /* Adjust size to express the common part of the scaling;
809 e.g. if xs = ys = 0.5 we scale size by 2.
811 if (xs > 1e-5 && ys > 1e-5) {
812 size *= 1 / min (1 / xs, 1 / ys);
816 /* Then express aspect ratio changes */
817 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
818 s.set_aspect_adjust (xs / ys);
821 s.set_in (dcp::Time(period.from.seconds(), 1000));
822 s.set_out (dcp::Time(period.to.seconds(), 1000));
823 ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
824 ps.add_fonts (piece->content->subtitle->fonts ());
827 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
828 _subtitles.push_back (make_pair (ps, period));
830 Subtitle (ps, period);
835 Player::seek (DCPTime time, bool accurate)
837 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
838 if (i->content->position() <= time && time < i->content->end()) {
839 i->decoder->seek (dcp_to_content_time (i, time), accurate);
845 _last_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
847 _last_time = optional<DCPTime> ();
851 shared_ptr<Resampler>
852 Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create)
854 ResamplerMap::const_iterator i = _resamplers.find (make_pair (content, stream));
855 if (i != _resamplers.end ()) {
860 return shared_ptr<Resampler> ();
864 "Creating new resampler from %1 to %2 with %3 channels",
865 stream->frame_rate(),
866 content->resampled_frame_rate(),
870 shared_ptr<Resampler> r (
871 new Resampler (stream->frame_rate(), content->resampled_frame_rate(), stream->channels())
874 _resamplers[make_pair(content, stream)] = r;