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);
99 seek (DCPTime (), true);
103 Player::setup_pieces ()
107 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
109 if (!i->paths_valid ()) {
113 shared_ptr<Decoder> decoder = decoder_factory (i, _film->log());
114 FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
117 /* Not something that we can decode; e.g. Atmos content */
121 if (decoder->video && _ignore_video) {
122 decoder->video->set_ignore ();
125 if (decoder->audio && _ignore_audio) {
126 decoder->audio->set_ignore ();
129 shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
130 if (dcp && _play_referenced) {
131 dcp->set_decode_referenced ();
134 shared_ptr<Piece> piece (new Piece (i, decoder, frc));
135 _pieces.push_back (piece);
137 if (decoder->video) {
138 decoder->video->Data.connect (bind (&Player::video, this, weak_ptr<Piece> (piece), _1));
141 if (decoder->audio) {
142 decoder->audio->Data.connect (bind (&Player::audio, this, weak_ptr<Piece> (piece), _1, _2));
145 if (decoder->subtitle) {
146 decoder->subtitle->ImageData.connect (bind (&Player::image_subtitle, this, weak_ptr<Piece> (piece), _1));
147 decoder->subtitle->TextData.connect (bind (&Player::text_subtitle, this, weak_ptr<Piece> (piece), _1));
151 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
152 if (i->content->audio) {
153 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams()) {
154 _stream_states[j] = StreamState (i, i->content->position ());
159 _have_valid_pieces = true;
163 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
165 shared_ptr<Content> c = w.lock ();
171 property == ContentProperty::POSITION ||
172 property == ContentProperty::LENGTH ||
173 property == ContentProperty::TRIM_START ||
174 property == ContentProperty::TRIM_END ||
175 property == ContentProperty::PATH ||
176 property == VideoContentProperty::FRAME_TYPE ||
177 property == DCPContentProperty::NEEDS_ASSETS ||
178 property == DCPContentProperty::NEEDS_KDM ||
179 property == SubtitleContentProperty::COLOUR ||
180 property == SubtitleContentProperty::OUTLINE ||
181 property == SubtitleContentProperty::SHADOW ||
182 property == SubtitleContentProperty::EFFECT_COLOUR ||
183 property == FFmpegContentProperty::SUBTITLE_STREAM ||
184 property == VideoContentProperty::COLOUR_CONVERSION
187 _have_valid_pieces = false;
191 property == SubtitleContentProperty::LINE_SPACING ||
192 property == SubtitleContentProperty::OUTLINE_WIDTH ||
193 property == SubtitleContentProperty::Y_SCALE ||
194 property == SubtitleContentProperty::FADE_IN ||
195 property == SubtitleContentProperty::FADE_OUT ||
196 property == ContentProperty::VIDEO_FRAME_RATE ||
197 property == SubtitleContentProperty::USE ||
198 property == SubtitleContentProperty::X_OFFSET ||
199 property == SubtitleContentProperty::Y_OFFSET ||
200 property == SubtitleContentProperty::X_SCALE ||
201 property == SubtitleContentProperty::FONTS ||
202 property == VideoContentProperty::CROP ||
203 property == VideoContentProperty::SCALE ||
204 property == VideoContentProperty::FADE_IN ||
205 property == VideoContentProperty::FADE_OUT
213 Player::set_video_container_size (dcp::Size s)
215 _video_container_size = s;
217 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
218 _black_image->make_black ();
222 Player::playlist_changed ()
224 _have_valid_pieces = false;
229 Player::film_changed (Film::Property p)
231 /* Here we should notice Film properties that affect our output, and
232 alert listeners that our output now would be different to how it was
233 last time we were run.
236 if (p == Film::CONTAINER) {
238 } else if (p == Film::VIDEO_FRAME_RATE) {
239 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
240 so we need new pieces here.
242 _have_valid_pieces = false;
244 } else if (p == Film::AUDIO_PROCESSOR) {
245 if (_film->audio_processor ()) {
246 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
252 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
254 list<PositionImage> all;
256 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
261 /* We will scale the subtitle up to fit _video_container_size */
262 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
264 /* Then we need a corrective translation, consisting of two parts:
266 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
267 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
269 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
270 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
271 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
273 * Combining these two translations gives these expressions.
280 dcp::YUV_TO_RGB_REC601,
281 i->image->pixel_format (),
286 lrint (_video_container_size.width * i->rectangle.x),
287 lrint (_video_container_size.height * i->rectangle.y)
296 shared_ptr<PlayerVideo>
297 Player::black_player_video_frame () const
299 return shared_ptr<PlayerVideo> (
301 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
304 _video_container_size,
305 _video_container_size,
308 PresetColourConversion::all().front().conversion
314 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
316 DCPTime s = t - piece->content->position ();
317 s = min (piece->content->length_after_trim(), s);
318 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
320 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
321 then convert that ContentTime to frames at the content's rate. However this fails for
322 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
323 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
325 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
327 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
331 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
333 /* See comment in dcp_to_content_video */
334 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
335 return max (DCPTime (), d + piece->content->position ());
339 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
341 DCPTime s = t - piece->content->position ();
342 s = min (piece->content->length_after_trim(), s);
343 /* See notes in dcp_to_content_video */
344 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
348 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
350 /* See comment in dcp_to_content_video */
351 DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start (), piece->frc);
352 return max (DCPTime (), d + piece->content->position ());
356 Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
358 DCPTime s = t - piece->content->position ();
359 s = min (piece->content->length_after_trim(), s);
360 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
364 Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
366 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
369 list<shared_ptr<Font> >
370 Player::get_subtitle_fonts ()
372 if (!_have_valid_pieces) {
376 list<shared_ptr<Font> > fonts;
377 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
378 if (p->content->subtitle) {
379 /* XXX: things may go wrong if there are duplicate font IDs
380 with different font files.
382 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
383 copy (f.begin(), f.end(), back_inserter (fonts));
390 /** Set this player never to produce any video data */
392 Player::set_ignore_video ()
394 _ignore_video = true;
397 /** Set this player never to produce any audio data */
399 Player::set_ignore_audio ()
401 _ignore_audio = true;
404 /** Set whether or not this player should always burn text subtitles into the image,
405 * regardless of the content settings.
406 * @param burn true to always burn subtitles, false to obey content settings.
409 Player::set_always_burn_subtitles (bool burn)
411 _always_burn_subtitles = burn;
418 _have_valid_pieces = false;
422 Player::set_play_referenced ()
424 _play_referenced = true;
425 _have_valid_pieces = false;
428 list<ReferencedReelAsset>
429 Player::get_reel_assets ()
431 list<ReferencedReelAsset> a;
433 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
434 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
439 scoped_ptr<DCPDecoder> decoder;
441 decoder.reset (new DCPDecoder (j, _film->log()));
447 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
449 DCPOMATIC_ASSERT (j->video_frame_rate ());
450 double const cfr = j->video_frame_rate().get();
451 Frame const trim_start = j->trim_start().frames_round (cfr);
452 Frame const trim_end = j->trim_end().frames_round (cfr);
453 int const ffr = _film->video_frame_rate ();
455 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
456 if (j->reference_video ()) {
457 shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
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_audio ()) {
467 shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
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 if (j->reference_subtitle ()) {
477 shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
478 DCPOMATIC_ASSERT (ra);
479 ra->set_entry_point (ra->entry_point() + trim_start);
480 ra->set_duration (ra->duration() - trim_start - trim_end);
482 ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
486 /* Assume that main picture duration is the length of the reel */
487 offset += k->main_picture()->duration ();
494 list<shared_ptr<Piece> >
495 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
497 if (!_have_valid_pieces) {
501 list<shared_ptr<Piece> > overlaps;
502 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
503 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
504 overlaps.push_back (i);
514 if (!_have_valid_pieces) {
518 shared_ptr<Piece> earliest;
519 DCPTime earliest_content;
521 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
523 DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
524 if (!earliest || t < earliest_content) {
525 earliest_content = t;
532 /* No more content; fill up to the length of our playlist with silent black */
534 DCPTime const length = _playlist->length ();
536 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
539 from = _last_time.get() + frame;
541 for (DCPTime i = from; i < length; i += frame) {
542 Video (black_player_video_frame (), i);
545 DCPTime t = _last_audio_time;
547 DCPTime block = min (DCPTime::from_seconds (0.5), length - t);
548 Frame const samples = block.frames_round(_film->audio_frame_rate());
550 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), samples));
551 silence->make_silent ();
560 earliest->done = earliest->decoder->pass ();
562 /* Emit any audio that is ready */
564 DCPTime pull_from = _playlist->length ();
565 for (map<AudioStreamPtr, StreamState>::const_iterator i = _stream_states.begin(); i != _stream_states.end(); ++i) {
566 if (!i->second.piece->done && i->second.last_push_end < pull_from) {
567 pull_from = i->second.last_push_end;
571 // cout << "PULL " << to_string(pull_from) << "\n";
572 pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (pull_from);
573 if (audio.first->frames() > 0) {
574 DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
575 DCPTime t = _last_audio_time;
576 while (t < audio.second) {
577 /* Silence up to the time of this new audio */
578 DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
579 shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
580 silence->make_silent ();
585 Audio (audio.first, audio.second);
586 _last_audio_time = audio.second + DCPTime::from_frames(audio.first->frames(), _film->audio_frame_rate());
593 Player::video (weak_ptr<Piece> wp, ContentVideo video)
595 shared_ptr<Piece> piece = wp.lock ();
600 FrameRateChange frc(piece->content->active_video_frame_rate(), _film->video_frame_rate());
601 if (frc.skip && (video.frame % 2) == 1) {
605 /* Time and period of the frame we will emit */
606 DCPTime const time = content_video_to_dcp (piece, video.frame);
607 DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
609 /* Discard if it's outside the content's period */
610 if (time < piece->content->position() || time >= piece->content->end()) {
614 /* Get any subtitles */
616 optional<PositionImage> subtitles;
618 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
620 if (!i->second.overlap (period)) {
624 list<PositionImage> sub_images;
626 /* Image subtitles */
627 list<PositionImage> c = transform_image_subtitles (i->first.image);
628 copy (c.begin(), c.end(), back_inserter (sub_images));
630 /* Text subtitles (rendered to an image) */
631 if (!i->first.text.empty ()) {
632 list<PositionImage> s = render_subtitles (i->first.text, i->first.fonts, _video_container_size, time);
633 copy (s.begin (), s.end (), back_inserter (sub_images));
636 if (!sub_images.empty ()) {
637 subtitles = merge (sub_images);
644 /* XXX: this may not work for 3D */
645 DCPTime const frame = DCPTime::from_frames (1, _film->video_frame_rate());
646 for (DCPTime i = _last_time.get() + frame; i < time; i += frame) {
647 if (_playlist->video_content_at(i) && _last_video) {
648 Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
650 Video (black_player_video_frame (), i);
658 piece->content->video->crop (),
659 piece->content->video->fade (video.frame),
660 piece->content->video->scale().size (
661 piece->content->video, _video_container_size, _film->frame_size ()
663 _video_container_size,
666 piece->content->video->colour_conversion ()
671 _last_video->set_subtitle (subtitles.get ());
676 Video (_last_video, *_last_time);
678 /* Discard any subtitles we no longer need */
680 for (list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator i = _subtitles.begin (); i != _subtitles.end(); ) {
681 list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator tmp = i;
684 if (i->second.to < time) {
685 _subtitles.erase (i);
693 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
695 shared_ptr<Piece> piece = wp.lock ();
700 shared_ptr<AudioContent> content = piece->content->audio;
701 DCPOMATIC_ASSERT (content);
704 if (content->gain() != 0) {
705 shared_ptr<AudioBuffers> gain (new AudioBuffers (content_audio.audio));
706 gain->apply_gain (content->gain ());
707 content_audio.audio = gain;
711 if (stream->frame_rate() != content->resampled_frame_rate()) {
712 shared_ptr<Resampler> r = resampler (content, stream, true);
713 pair<shared_ptr<const AudioBuffers>, Frame> ro = r->run (content_audio.audio, content_audio.frame);
714 content_audio.audio = ro.first;
715 content_audio.frame = ro.second;
718 /* XXX: end-trimming used to be checked here */
720 /* Compute time in the DCP */
721 DCPTime time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000.0);
723 /* Remove anything that comes before the start of the content */
724 if (time < piece->content->position()) {
725 DCPTime const discard_time = piece->content->position() - time;
726 Frame discard_frames = discard_time.frames_round(_film->audio_frame_rate());
727 Frame remaining_frames = content_audio.audio->frames() - discard_frames;
728 if (remaining_frames <= 0) {
729 /* This audio is entirely discarded */
732 shared_ptr<AudioBuffers> cut (new AudioBuffers (content_audio.audio->channels(), remaining_frames));
733 cut->copy_from (content_audio.audio.get(), remaining_frames, discard_frames, 0);
734 content_audio.audio = cut;
735 time += discard_time;
739 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), content_audio.audio->frames()));
740 dcp_mapped->make_silent ();
742 AudioMapping map = stream->mapping ();
743 for (int i = 0; i < map.input_channels(); ++i) {
744 for (int j = 0; j < dcp_mapped->channels(); ++j) {
745 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
746 dcp_mapped->accumulate_channel (
747 content_audio.audio.get(),
749 static_cast<dcp::Channel> (j),
750 map.get (i, static_cast<dcp::Channel> (j))
756 content_audio.audio = dcp_mapped;
758 if (_audio_processor) {
759 content_audio.audio = _audio_processor->run (content_audio.audio, _film->audio_channels ());
762 // cout << "PUSH " << content_audio.audio->frames() << " @ " << to_string(time) << "\n";
763 _audio_merger.push (content_audio.audio, time);
765 DCPOMATIC_ASSERT (_stream_states.find (stream) != _stream_states.end ());
766 _stream_states[stream].last_push_end = time + DCPTime::from_frames (content_audio.audio->frames(), _film->audio_frame_rate());
770 Player::image_subtitle (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
772 shared_ptr<Piece> piece = wp.lock ();
777 /* Apply content's subtitle offsets */
778 subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
779 subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
781 /* Apply content's subtitle scale */
782 subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
783 subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
785 /* Apply a corrective translation to keep the subtitle centred after that scale */
786 subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * (piece->content->subtitle->x_scale() - 1);
787 subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * (piece->content->subtitle->y_scale() - 1);
790 ps.image.push_back (subtitle.sub);
791 DCPTimePeriod period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
793 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
794 _subtitles.push_back (make_pair (ps, period));
796 Subtitle (ps, period);
801 Player::text_subtitle (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
803 shared_ptr<Piece> piece = wp.lock ();
809 DCPTimePeriod const period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
811 BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
812 s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
813 s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
814 float const xs = piece->content->subtitle->x_scale();
815 float const ys = piece->content->subtitle->y_scale();
816 float size = s.size();
818 /* Adjust size to express the common part of the scaling;
819 e.g. if xs = ys = 0.5 we scale size by 2.
821 if (xs > 1e-5 && ys > 1e-5) {
822 size *= 1 / min (1 / xs, 1 / ys);
826 /* Then express aspect ratio changes */
827 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
828 s.set_aspect_adjust (xs / ys);
831 s.set_in (dcp::Time(period.from.seconds(), 1000));
832 s.set_out (dcp::Time(period.to.seconds(), 1000));
833 ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
834 ps.add_fonts (piece->content->subtitle->fonts ());
837 if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
838 _subtitles.push_back (make_pair (ps, period));
840 Subtitle (ps, period);
845 Player::seek (DCPTime time, bool accurate)
847 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
848 if (i->content->position() <= time && time < i->content->end()) {
849 i->decoder->seek (dcp_to_content_time (i, time), accurate);
855 _last_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
857 _last_time = optional<DCPTime> ();
861 shared_ptr<Resampler>
862 Player::resampler (shared_ptr<const AudioContent> content, AudioStreamPtr stream, bool create)
864 ResamplerMap::const_iterator i = _resamplers.find (make_pair (content, stream));
865 if (i != _resamplers.end ()) {
870 return shared_ptr<Resampler> ();
874 "Creating new resampler from %1 to %2 with %3 channels",
875 stream->frame_rate(),
876 content->resampled_frame_rate(),
880 shared_ptr<Resampler> r (
881 new Resampler (stream->frame_rate(), content->resampled_frame_rate(), stream->channels())
884 _resamplers[make_pair(content, stream)] = r;