2 Copyright (C) 2013-2015 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 "ffmpeg_content.h"
25 #include "image_decoder.h"
26 #include "image_content.h"
27 #include "sndfile_decoder.h"
28 #include "sndfile_content.h"
29 #include "subtitle_content.h"
30 #include "text_subtitle_decoder.h"
31 #include "text_subtitle_content.h"
32 #include "dcp_content.h"
35 #include "raw_image_proxy.h"
38 #include "render_subtitles.h"
40 #include "content_video.h"
41 #include "player_video.h"
42 #include "frame_rate_change.h"
43 #include "dcp_content.h"
44 #include "dcp_decoder.h"
45 #include "dcp_subtitle_content.h"
46 #include "dcp_subtitle_decoder.h"
47 #include "audio_processor.h"
49 #include "referenced_reel_asset.h"
51 #include <dcp/reel_sound_asset.h>
52 #include <dcp/reel_subtitle_asset.h>
53 #include <dcp/reel_picture_asset.h>
54 #include <boost/foreach.hpp>
61 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
73 using boost::shared_ptr;
74 using boost::weak_ptr;
75 using boost::dynamic_pointer_cast;
76 using boost::optional;
77 using boost::scoped_ptr;
79 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
81 , _playlist (playlist)
82 , _have_valid_pieces (false)
83 , _ignore_video (false)
84 , _ignore_audio (false)
85 , _always_burn_subtitles (false)
87 , _play_referenced (false)
89 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
90 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
91 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
92 set_video_container_size (_film->frame_size ());
94 film_changed (Film::AUDIO_PROCESSOR);
98 Player::setup_pieces ()
100 list<shared_ptr<Piece> > old_pieces = _pieces;
103 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
105 if (!i->paths_valid ()) {
109 shared_ptr<Decoder> decoder;
110 optional<FrameRateChange> frc;
113 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i);
115 decoder.reset (new FFmpegDecoder (fc, _film->log(), _fast));
116 frc = FrameRateChange (fc->video->video_frame_rate(), _film->video_frame_rate());
119 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
121 decoder.reset (new DCPDecoder (dc, _film->log(), _fast));
122 frc = FrameRateChange (dc->video->video_frame_rate(), _film->video_frame_rate());
126 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
128 /* See if we can re-use an old ImageDecoder */
129 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
130 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
131 if (imd && imd->content() == ic) {
137 decoder.reset (new ImageDecoder (ic, _film->log()));
140 frc = FrameRateChange (ic->video->video_frame_rate(), _film->video_frame_rate());
144 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i);
146 decoder.reset (new SndfileDecoder (sc, _fast));
148 /* Work out a FrameRateChange for the best overlap video for this content */
149 DCPTime best_overlap_t;
150 shared_ptr<Content> best_overlap;
151 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
156 DCPTime const overlap = min (j->end(), i->end()) - max (j->position(), i->position());
157 if (overlap > best_overlap_t) {
159 best_overlap_t = overlap;
164 frc = FrameRateChange (best_overlap->video->video_frame_rate(), _film->video_frame_rate ());
166 /* No video overlap; e.g. if the DCP is just audio */
167 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
171 /* It's questionable whether subtitle content should have a video frame rate; perhaps
172 it should be assumed that any subtitle content has been prepared at the same rate
173 as simultaneous video content (like we do with audio).
176 /* TextSubtitleContent */
177 shared_ptr<const TextSubtitleContent> rc = dynamic_pointer_cast<const TextSubtitleContent> (i);
179 decoder.reset (new TextSubtitleDecoder (rc));
180 frc = FrameRateChange (rc->subtitle_video_frame_rate(), _film->video_frame_rate());
183 /* DCPSubtitleContent */
184 shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (i);
186 decoder.reset (new DCPSubtitleDecoder (dsc));
187 frc = FrameRateChange (dsc->subtitle_video_frame_rate(), _film->video_frame_rate());
190 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> (decoder);
191 if (vd && _ignore_video) {
192 vd->set_ignore_video ();
195 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> (decoder);
196 if (ad && _ignore_audio) {
197 ad->set_ignore_audio ();
200 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc.get ())));
203 _have_valid_pieces = true;
207 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
209 shared_ptr<Content> c = w.lock ();
215 property == ContentProperty::POSITION ||
216 property == ContentProperty::LENGTH ||
217 property == ContentProperty::TRIM_START ||
218 property == ContentProperty::TRIM_END ||
219 property == ContentProperty::PATH ||
220 property == VideoContentProperty::VIDEO_FRAME_TYPE ||
221 property == DCPContentProperty::CAN_BE_PLAYED ||
222 property == TextSubtitleContentProperty::TEXT_SUBTITLE_COLOUR ||
223 property == TextSubtitleContentProperty::TEXT_SUBTITLE_OUTLINE ||
224 property == TextSubtitleContentProperty::TEXT_SUBTITLE_OUTLINE_COLOUR ||
225 property == FFmpegContentProperty::SUBTITLE_STREAM
228 _have_valid_pieces = false;
232 property == SubtitleContentProperty::USE_SUBTITLES ||
233 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
234 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
235 property == SubtitleContentProperty::SUBTITLE_X_SCALE ||
236 property == SubtitleContentProperty::SUBTITLE_Y_SCALE ||
237 property == SubtitleContentProperty::FONTS ||
238 property == VideoContentProperty::VIDEO_CROP ||
239 property == VideoContentProperty::VIDEO_SCALE ||
240 property == VideoContentProperty::VIDEO_FRAME_RATE ||
241 property == VideoContentProperty::VIDEO_FADE_IN ||
242 property == VideoContentProperty::VIDEO_FADE_OUT ||
243 property == VideoContentProperty::COLOUR_CONVERSION
251 Player::set_video_container_size (dcp::Size s)
253 _video_container_size = s;
255 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
256 _black_image->make_black ();
260 Player::playlist_changed ()
262 _have_valid_pieces = false;
267 Player::film_changed (Film::Property p)
269 /* Here we should notice Film properties that affect our output, and
270 alert listeners that our output now would be different to how it was
271 last time we were run.
274 if (p == Film::CONTAINER) {
276 } else if (p == Film::VIDEO_FRAME_RATE) {
277 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
278 so we need new pieces here.
280 _have_valid_pieces = false;
282 } else if (p == Film::AUDIO_PROCESSOR) {
283 if (_film->audio_processor ()) {
284 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
290 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
292 list<PositionImage> all;
294 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
299 /* We will scale the subtitle up to fit _video_container_size */
300 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
302 /* Then we need a corrective translation, consisting of two parts:
304 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
305 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
307 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
308 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
309 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
311 * Combining these two translations gives these expressions.
318 dcp::YUV_TO_RGB_REC601,
319 i->image->pixel_format (),
324 lrint (_video_container_size.width * i->rectangle.x),
325 lrint (_video_container_size.height * i->rectangle.y)
334 shared_ptr<PlayerVideo>
335 Player::black_player_video_frame (DCPTime time) const
337 return shared_ptr<PlayerVideo> (
339 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
343 _video_container_size,
344 _video_container_size,
347 PresetColourConversion::all().front().conversion
352 /** @return All PlayerVideos at the given time. There may be none if the content
353 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
354 * or 2 if we have 3D.
356 list<shared_ptr<PlayerVideo> >
357 Player::get_video (DCPTime time, bool accurate)
359 if (!_have_valid_pieces) {
363 /* Find subtitles for possible burn-in */
365 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
367 list<PositionImage> sub_images;
369 /* Image subtitles */
370 list<PositionImage> c = transform_image_subtitles (ps.image);
371 copy (c.begin(), c.end(), back_inserter (sub_images));
373 /* Text subtitles (rendered to an image) */
374 if (!ps.text.empty ()) {
375 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
376 copy (s.begin (), s.end (), back_inserter (sub_images));
379 optional<PositionImage> subtitles;
380 if (!sub_images.empty ()) {
381 subtitles = merge (sub_images);
384 /* Find pieces containing video which is happening now */
386 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
388 time + DCPTime::from_frames (1, _film->video_frame_rate ())
391 list<shared_ptr<PlayerVideo> > pvf;
394 /* No video content at this time */
395 pvf.push_back (black_player_video_frame (time));
397 /* Some video content at this time */
398 shared_ptr<Piece> last = *(ov.rbegin ());
399 VideoFrameType const last_type = last->content->video->video_frame_type ();
401 /* Get video from appropriate piece(s) */
402 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
404 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
405 DCPOMATIC_ASSERT (decoder);
407 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
408 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
413 /* always use the last video */
415 /* with a corresponding L/R eye if appropriate */
416 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
417 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->video_frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
420 /* We want to use this piece */
421 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
422 if (content_video.empty ()) {
423 pvf.push_back (black_player_video_frame (time));
425 dcp::Size image_size = piece->content->video->scale().size (
426 piece->content->video, _video_container_size, _film->frame_size ()
429 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
431 shared_ptr<PlayerVideo> (
434 content_video_to_dcp (piece, i->frame),
435 piece->content->video->crop (),
436 piece->content->video->fade (i->frame),
438 _video_container_size,
441 piece->content->video->colour_conversion ()
448 /* Discard unused video */
449 decoder->get_video (dcp_to_content_video (piece, time), accurate);
455 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
456 p->set_subtitle (subtitles.get ());
463 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
464 shared_ptr<AudioBuffers>
465 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
467 if (!_have_valid_pieces) {
471 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
473 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
474 audio->make_silent ();
476 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
481 bool all_referenced = true;
482 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
483 shared_ptr<AudioContent> audio_content = dynamic_pointer_cast<AudioContent> (i->content);
484 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
485 if (audio_content && (!dcp_content || !dcp_content->reference_audio ())) {
486 /* There is audio content which is not from a DCP or not set to be referenced */
487 all_referenced = false;
491 if (all_referenced && !_play_referenced) {
492 return shared_ptr<AudioBuffers> ();
495 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
497 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (i->content);
498 DCPOMATIC_ASSERT (content);
499 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> (i->decoder);
500 DCPOMATIC_ASSERT (decoder);
502 /* The time that we should request from the content */
503 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
504 Frame request_frames = length_frames;
506 if (request < DCPTime ()) {
507 /* We went off the start of the content, so we will need to offset
508 the stuff we get back.
511 request_frames += request.frames_round (_film->audio_frame_rate ());
512 if (request_frames < 0) {
515 request = DCPTime ();
518 Frame const content_frame = dcp_to_resampled_audio (i, request);
520 BOOST_FOREACH (AudioStreamPtr j, content->audio_streams ()) {
522 if (j->channels() == 0) {
523 /* Some content (e.g. DCPs) can have streams with no channels */
527 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
528 ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
531 if (content->audio_gain() != 0) {
532 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
533 gain->apply_gain (content->audio_gain ());
538 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
539 dcp_mapped->make_silent ();
540 AudioMapping map = j->mapping ();
541 for (int i = 0; i < map.input_channels(); ++i) {
542 for (int j = 0; j < _film->audio_channels(); ++j) {
543 if (map.get (i, j) > 0) {
544 dcp_mapped->accumulate_channel (
554 if (_audio_processor) {
555 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
558 all.audio = dcp_mapped;
560 audio->accumulate_frames (
562 content_frame - all.frame,
563 offset.frames_round (_film->audio_frame_rate()),
564 min (Frame (all.audio->frames()), request_frames)
573 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
575 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
576 DCPTime s = t - piece->content->position ();
577 s = min (piece->content->length_after_trim(), s);
578 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
580 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
581 then convert that ContentTime to frames at the content's rate. However this fails for
582 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
583 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
585 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
587 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
591 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
593 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
594 /* See comment in dcp_to_content_video */
595 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
596 return max (DCPTime (), d + piece->content->position ());
600 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
602 DCPTime s = t - piece->content->position ();
603 s = min (piece->content->length_after_trim(), s);
604 /* See notes in dcp_to_content_video */
605 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
609 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
611 DCPTime s = t - piece->content->position ();
612 s = min (piece->content->length_after_trim(), s);
613 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
617 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
619 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
622 /** @param burnt true to return only subtitles to be burnt, false to return only
623 * subtitles that should not be burnt. This parameter will be ignored if
624 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
627 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
629 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
631 PlayerSubtitles ps (time, length);
633 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
634 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
635 if (!subtitle_content->use_subtitles () || (!_always_burn_subtitles && (burnt != subtitle_content->burn_subtitles ()))) {
639 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (subtitle_content);
640 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
644 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder);
645 ContentTime const from = dcp_to_content_subtitle (*j, time);
646 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
647 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
649 list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting, accurate);
650 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
652 /* Apply content's subtitle offsets */
653 i->sub.rectangle.x += subtitle_content->subtitle_x_offset ();
654 i->sub.rectangle.y += subtitle_content->subtitle_y_offset ();
656 /* Apply content's subtitle scale */
657 i->sub.rectangle.width *= subtitle_content->subtitle_x_scale ();
658 i->sub.rectangle.height *= subtitle_content->subtitle_y_scale ();
660 /* Apply a corrective translation to keep the subtitle centred after that scale */
661 i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1);
662 i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1);
664 ps.image.push_back (i->sub);
667 list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting, accurate);
668 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
669 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
670 s.set_h_position (s.h_position() + subtitle_content->subtitle_x_offset ());
671 s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ());
672 float const xs = subtitle_content->subtitle_x_scale();
673 float const ys = subtitle_content->subtitle_y_scale();
674 float size = s.size();
676 /* Adjust size to express the common part of the scaling;
677 e.g. if xs = ys = 0.5 we scale size by 2.
679 if (xs > 1e-5 && ys > 1e-5) {
680 size *= 1 / min (1 / xs, 1 / ys);
684 /* Then express aspect ratio changes */
685 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
686 s.set_aspect_adjust (xs / ys);
688 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
689 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
690 ps.text.push_back (s);
691 ps.add_fonts (subtitle_content->fonts ());
699 list<shared_ptr<Font> >
700 Player::get_subtitle_fonts ()
702 if (!_have_valid_pieces) {
706 list<shared_ptr<Font> > fonts;
707 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
708 shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (p->content);
710 /* XXX: things may go wrong if there are duplicate font IDs
711 with different font files.
713 list<shared_ptr<Font> > f = sc->fonts ();
714 copy (f.begin(), f.end(), back_inserter (fonts));
721 /** Set this player never to produce any video data */
723 Player::set_ignore_video ()
725 _ignore_video = true;
728 /** Set this player never to produce any audio data */
730 Player::set_ignore_audio ()
732 _ignore_audio = true;
735 /** Set whether or not this player should always burn text subtitles into the image,
736 * regardless of the content settings.
737 * @param burn true to always burn subtitles, false to obey content settings.
740 Player::set_always_burn_subtitles (bool burn)
742 _always_burn_subtitles = burn;
749 _have_valid_pieces = false;
753 Player::set_play_referenced ()
755 _play_referenced = true;
756 _have_valid_pieces = false;
759 list<ReferencedReelAsset>
760 Player::get_reel_assets ()
762 list<ReferencedReelAsset> a;
764 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
765 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
770 scoped_ptr<DCPDecoder> decoder;
772 decoder.reset (new DCPDecoder (j, _film->log(), false));
778 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
779 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
780 if (j->reference_video ()) {
782 ReferencedReelAsset (
784 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
789 if (j->reference_audio ()) {
791 ReferencedReelAsset (
793 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
798 if (j->reference_subtitle ()) {
799 DCPOMATIC_ASSERT (k->main_subtitle ());
801 ReferencedReelAsset (
803 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
808 /* Assume that main picture duration is the length of the reel */
809 offset += k->main_picture()->duration ();