2 Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "ffmpeg_decoder.h"
23 #include "audio_buffers.h"
24 #include "audio_content.h"
25 #include "ffmpeg_content.h"
26 #include "image_decoder.h"
27 #include "image_content.h"
28 #include "sndfile_decoder.h"
29 #include "sndfile_content.h"
30 #include "subtitle_content.h"
31 #include "text_subtitle_decoder.h"
32 #include "text_subtitle_content.h"
33 #include "dcp_content.h"
36 #include "raw_image_proxy.h"
39 #include "render_subtitles.h"
41 #include "content_video.h"
42 #include "player_video.h"
43 #include "frame_rate_change.h"
44 #include "dcp_content.h"
45 #include "dcp_decoder.h"
46 #include "dcp_subtitle_content.h"
47 #include "dcp_subtitle_decoder.h"
48 #include "audio_processor.h"
50 #include "referenced_reel_asset.h"
52 #include <dcp/reel_sound_asset.h>
53 #include <dcp/reel_subtitle_asset.h>
54 #include <dcp/reel_picture_asset.h>
55 #include <boost/foreach.hpp>
62 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
74 using boost::shared_ptr;
75 using boost::weak_ptr;
76 using boost::dynamic_pointer_cast;
77 using boost::optional;
78 using boost::scoped_ptr;
81 has_video (Content* c)
83 return static_cast<bool>(c->video);
87 has_audio (Content* c)
89 return static_cast<bool>(c->audio);
93 has_subtitle (Content* c)
95 return static_cast<bool>(c->subtitle);
98 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
100 , _playlist (playlist)
101 , _have_valid_pieces (false)
102 , _ignore_video (false)
103 , _ignore_audio (false)
104 , _always_burn_subtitles (false)
106 , _play_referenced (false)
108 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
109 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
110 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
111 set_video_container_size (_film->frame_size ());
113 film_changed (Film::AUDIO_PROCESSOR);
117 Player::setup_pieces ()
119 list<shared_ptr<Piece> > old_pieces = _pieces;
122 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
124 if (!i->paths_valid ()) {
128 shared_ptr<Decoder> decoder;
129 optional<FrameRateChange> frc;
132 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i);
134 decoder.reset (new FFmpegDecoder (fc, _film->log(), _fast));
135 frc = FrameRateChange (fc->active_video_frame_rate(), _film->video_frame_rate());
138 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
140 decoder.reset (new DCPDecoder (dc, _film->log(), _fast));
141 frc = FrameRateChange (dc->active_video_frame_rate(), _film->video_frame_rate());
145 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
147 /* See if we can re-use an old ImageDecoder */
148 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
149 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
150 if (imd && imd->content() == ic) {
156 decoder.reset (new ImageDecoder (ic, _film->log()));
159 frc = FrameRateChange (ic->active_video_frame_rate(), _film->video_frame_rate());
163 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i);
165 decoder.reset (new SndfileDecoder (sc, _fast, _film->log()));
167 /* Work out a FrameRateChange for the best overlap video for this content */
168 DCPTime best_overlap_t;
169 shared_ptr<Content> best_overlap;
170 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
175 DCPTime const overlap = min (j->end(), i->end()) - max (j->position(), i->position());
176 if (overlap > best_overlap_t) {
178 best_overlap_t = overlap;
183 frc = FrameRateChange (best_overlap->active_video_frame_rate(), _film->video_frame_rate ());
185 /* No video overlap; e.g. if the DCP is just audio */
186 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
190 /* It's questionable whether subtitle content should have a video frame rate; perhaps
191 it should be assumed that any subtitle content has been prepared at the same rate
192 as simultaneous video content (like we do with audio).
195 /* TextSubtitleContent */
196 shared_ptr<const TextSubtitleContent> rc = dynamic_pointer_cast<const TextSubtitleContent> (i);
198 decoder.reset (new TextSubtitleDecoder (rc));
199 frc = FrameRateChange (rc->active_video_frame_rate(), _film->video_frame_rate());
202 /* DCPSubtitleContent */
203 shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (i);
205 decoder.reset (new DCPSubtitleDecoder (dsc));
206 frc = FrameRateChange (dsc->active_video_frame_rate(), _film->video_frame_rate());
209 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> (decoder);
210 if (vd && _ignore_video) {
211 vd->set_ignore_video ();
214 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> (decoder);
215 if (ad && _ignore_audio) {
216 ad->set_ignore_audio ();
219 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc.get ())));
222 _have_valid_pieces = true;
226 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
228 shared_ptr<Content> c = w.lock ();
234 property == ContentProperty::POSITION ||
235 property == ContentProperty::LENGTH ||
236 property == ContentProperty::TRIM_START ||
237 property == ContentProperty::TRIM_END ||
238 property == ContentProperty::PATH ||
239 property == VideoContentProperty::FRAME_TYPE ||
240 property == DCPContentProperty::CAN_BE_PLAYED ||
241 property == SubtitleContentProperty::COLOUR ||
242 property == SubtitleContentProperty::OUTLINE ||
243 property == SubtitleContentProperty::OUTLINE_COLOUR ||
244 property == FFmpegContentProperty::SUBTITLE_STREAM
247 _have_valid_pieces = false;
251 property == ContentProperty::VIDEO_FRAME_RATE ||
252 property == SubtitleContentProperty::USE ||
253 property == SubtitleContentProperty::X_OFFSET ||
254 property == SubtitleContentProperty::Y_OFFSET ||
255 property == SubtitleContentProperty::X_SCALE ||
256 property == SubtitleContentProperty::Y_SCALE ||
257 property == SubtitleContentProperty::FONTS ||
258 property == VideoContentProperty::CROP ||
259 property == VideoContentProperty::SCALE ||
260 property == VideoContentProperty::FADE_IN ||
261 property == VideoContentProperty::FADE_OUT ||
262 property == VideoContentProperty::COLOUR_CONVERSION
270 Player::set_video_container_size (dcp::Size s)
272 _video_container_size = s;
274 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
275 _black_image->make_black ();
279 Player::playlist_changed ()
281 _have_valid_pieces = false;
286 Player::film_changed (Film::Property p)
288 /* Here we should notice Film properties that affect our output, and
289 alert listeners that our output now would be different to how it was
290 last time we were run.
293 if (p == Film::CONTAINER) {
295 } else if (p == Film::VIDEO_FRAME_RATE) {
296 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
297 so we need new pieces here.
299 _have_valid_pieces = false;
301 } else if (p == Film::AUDIO_PROCESSOR) {
302 if (_film->audio_processor ()) {
303 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
309 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
311 list<PositionImage> all;
313 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
318 /* We will scale the subtitle up to fit _video_container_size */
319 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
321 /* Then we need a corrective translation, consisting of two parts:
323 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
324 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
326 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
327 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
328 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
330 * Combining these two translations gives these expressions.
337 dcp::YUV_TO_RGB_REC601,
338 i->image->pixel_format (),
343 lrint (_video_container_size.width * i->rectangle.x),
344 lrint (_video_container_size.height * i->rectangle.y)
353 shared_ptr<PlayerVideo>
354 Player::black_player_video_frame (DCPTime time) const
356 return shared_ptr<PlayerVideo> (
358 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
362 _video_container_size,
363 _video_container_size,
366 PresetColourConversion::all().front().conversion
371 /** @return All PlayerVideos at the given time. There may be none if the content
372 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
373 * or 2 if we have 3D.
375 list<shared_ptr<PlayerVideo> >
376 Player::get_video (DCPTime time, bool accurate)
378 if (!_have_valid_pieces) {
382 /* Find subtitles for possible burn-in */
384 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
386 list<PositionImage> sub_images;
388 /* Image subtitles */
389 list<PositionImage> c = transform_image_subtitles (ps.image);
390 copy (c.begin(), c.end(), back_inserter (sub_images));
392 /* Text subtitles (rendered to an image) */
393 if (!ps.text.empty ()) {
394 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
395 copy (s.begin (), s.end (), back_inserter (sub_images));
398 optional<PositionImage> subtitles;
399 if (!sub_images.empty ()) {
400 subtitles = merge (sub_images);
403 /* Find pieces containing video which is happening now */
405 list<shared_ptr<Piece> > ov = overlaps (
407 time + DCPTime::from_frames (1, _film->video_frame_rate ()),
411 list<shared_ptr<PlayerVideo> > pvf;
414 /* No video content at this time */
415 pvf.push_back (black_player_video_frame (time));
417 /* Some video content at this time */
418 shared_ptr<Piece> last = *(ov.rbegin ());
419 VideoFrameType const last_type = last->content->video->frame_type ();
421 /* Get video from appropriate piece(s) */
422 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
424 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
425 DCPOMATIC_ASSERT (decoder);
427 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
428 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
433 /* always use the last video */
435 /* with a corresponding L/R eye if appropriate */
436 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
437 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
440 /* We want to use this piece */
441 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
442 if (content_video.empty ()) {
443 pvf.push_back (black_player_video_frame (time));
445 dcp::Size image_size = piece->content->video->scale().size (
446 piece->content->video, _video_container_size, _film->frame_size ()
449 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
451 shared_ptr<PlayerVideo> (
454 content_video_to_dcp (piece, i->frame),
455 piece->content->video->crop (),
456 piece->content->video->fade (i->frame),
458 _video_container_size,
461 piece->content->video->colour_conversion ()
468 /* Discard unused video */
469 decoder->get_video (dcp_to_content_video (piece, time), accurate);
475 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
476 p->set_subtitle (subtitles.get ());
483 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
484 shared_ptr<AudioBuffers>
485 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
487 if (!_have_valid_pieces) {
491 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
493 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
494 audio->make_silent ();
496 list<shared_ptr<Piece> > ov = overlaps (time, time + length, has_audio);
501 bool all_referenced = true;
502 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
503 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
504 if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) {
505 /* There is audio content which is not from a DCP or not set to be referenced */
506 all_referenced = false;
510 if (all_referenced && !_play_referenced) {
511 return shared_ptr<AudioBuffers> ();
514 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
516 DCPOMATIC_ASSERT (i->content->audio);
517 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> (i->decoder);
518 DCPOMATIC_ASSERT (decoder);
520 /* The time that we should request from the content */
521 DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0);
522 Frame request_frames = length_frames;
524 if (request < DCPTime ()) {
525 /* We went off the start of the content, so we will need to offset
526 the stuff we get back.
529 request_frames += request.frames_round (_film->audio_frame_rate ());
530 if (request_frames < 0) {
533 request = DCPTime ();
536 Frame const content_frame = dcp_to_resampled_audio (i, request);
538 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) {
540 if (j->channels() == 0) {
541 /* Some content (e.g. DCPs) can have streams with no channels */
545 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
546 ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
549 if (i->content->audio->gain() != 0) {
550 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
551 gain->apply_gain (i->content->audio->gain ());
556 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
557 dcp_mapped->make_silent ();
558 AudioMapping map = j->mapping ();
559 for (int i = 0; i < map.input_channels(); ++i) {
560 for (int j = 0; j < _film->audio_channels(); ++j) {
561 if (map.get (i, j) > 0) {
562 dcp_mapped->accumulate_channel (
572 if (_audio_processor) {
573 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
576 all.audio = dcp_mapped;
578 audio->accumulate_frames (
580 content_frame - all.frame,
581 offset.frames_round (_film->audio_frame_rate()),
582 min (Frame (all.audio->frames()), request_frames)
591 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
593 DCPTime s = t - piece->content->position ();
594 s = min (piece->content->length_after_trim(), s);
595 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
597 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
598 then convert that ContentTime to frames at the content's rate. However this fails for
599 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
600 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
602 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
604 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
608 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
610 /* See comment in dcp_to_content_video */
611 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
612 return max (DCPTime (), d + piece->content->position ());
616 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
618 DCPTime s = t - piece->content->position ();
619 s = min (piece->content->length_after_trim(), s);
620 /* See notes in dcp_to_content_video */
621 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
625 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
627 DCPTime s = t - piece->content->position ();
628 s = min (piece->content->length_after_trim(), s);
629 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
633 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
635 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
638 /** @param burnt true to return only subtitles to be burnt, false to return only
639 * subtitles that should not be burnt. This parameter will be ignored if
640 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
643 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
645 list<shared_ptr<Piece> > subs = overlaps (time, time + length, has_subtitle);
647 PlayerSubtitles ps (time, length);
649 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
650 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
654 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->content);
655 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
659 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder);
660 ContentTime const from = dcp_to_content_subtitle (*j, time);
661 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
662 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
664 list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting, accurate);
665 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
667 /* Apply content's subtitle offsets */
668 i->sub.rectangle.x += (*j)->content->subtitle->x_offset ();
669 i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
671 /* Apply content's subtitle scale */
672 i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
673 i->sub.rectangle.height *= (*j)->content->subtitle->y_scale ();
675 /* Apply a corrective translation to keep the subtitle centred after that scale */
676 i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1);
677 i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1);
679 ps.image.push_back (i->sub);
682 list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting, accurate);
683 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
684 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
685 s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ());
686 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
687 float const xs = (*j)->content->subtitle->x_scale();
688 float const ys = (*j)->content->subtitle->y_scale();
689 float size = s.size();
691 /* Adjust size to express the common part of the scaling;
692 e.g. if xs = ys = 0.5 we scale size by 2.
694 if (xs > 1e-5 && ys > 1e-5) {
695 size *= 1 / min (1 / xs, 1 / ys);
699 /* Then express aspect ratio changes */
700 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
701 s.set_aspect_adjust (xs / ys);
703 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
704 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
705 ps.text.push_back (s);
706 ps.add_fonts ((*j)->content->subtitle->fonts ());
714 list<shared_ptr<Font> >
715 Player::get_subtitle_fonts ()
717 if (!_have_valid_pieces) {
721 list<shared_ptr<Font> > fonts;
722 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
723 if (p->content->subtitle) {
724 /* XXX: things may go wrong if there are duplicate font IDs
725 with different font files.
727 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
728 copy (f.begin(), f.end(), back_inserter (fonts));
735 /** Set this player never to produce any video data */
737 Player::set_ignore_video ()
739 _ignore_video = true;
742 /** Set this player never to produce any audio data */
744 Player::set_ignore_audio ()
746 _ignore_audio = true;
749 /** Set whether or not this player should always burn text subtitles into the image,
750 * regardless of the content settings.
751 * @param burn true to always burn subtitles, false to obey content settings.
754 Player::set_always_burn_subtitles (bool burn)
756 _always_burn_subtitles = burn;
763 _have_valid_pieces = false;
767 Player::set_play_referenced ()
769 _play_referenced = true;
770 _have_valid_pieces = false;
773 list<ReferencedReelAsset>
774 Player::get_reel_assets ()
776 list<ReferencedReelAsset> a;
778 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
779 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
784 scoped_ptr<DCPDecoder> decoder;
786 decoder.reset (new DCPDecoder (j, _film->log(), false));
792 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
793 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
794 if (j->reference_video ()) {
796 ReferencedReelAsset (
798 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
803 if (j->reference_audio ()) {
805 ReferencedReelAsset (
807 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
812 if (j->reference_subtitle ()) {
813 DCPOMATIC_ASSERT (k->main_subtitle ());
815 ReferencedReelAsset (
817 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
822 /* Assume that main picture duration is the length of the reel */
823 offset += k->main_picture()->duration ();
830 list<shared_ptr<Piece> >
831 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
833 if (!_have_valid_pieces) {
837 list<shared_ptr<Piece> > overlaps;
838 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
839 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
840 overlaps.push_back (i);