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_frame_rate(), _film->video_frame_rate());
119 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
121 decoder.reset (new DCPDecoder (dc, _fast));
122 frc = FrameRateChange (dc->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));
140 frc = FrameRateChange (ic->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<VideoContent> best_overlap;
151 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
152 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (j);
157 DCPTime const overlap = min (vc->end(), i->end()) - max (vc->position(), i->position());
158 if (overlap > best_overlap_t) {
160 best_overlap_t = overlap;
165 frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
167 /* No video overlap; e.g. if the DCP is just audio */
168 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
172 /* It's questionable whether subtitle content should have a video frame rate; perhaps
173 it should be assumed that any subtitle content has been prepared at the same rate
174 as simultaneous video content (like we do with audio).
177 /* TextSubtitleContent */
178 shared_ptr<const TextSubtitleContent> rc = dynamic_pointer_cast<const TextSubtitleContent> (i);
180 decoder.reset (new TextSubtitleDecoder (rc));
181 frc = FrameRateChange (rc->subtitle_video_frame_rate(), _film->video_frame_rate());
184 /* DCPSubtitleContent */
185 shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (i);
187 decoder.reset (new DCPSubtitleDecoder (dsc));
188 frc = FrameRateChange (dsc->subtitle_video_frame_rate(), _film->video_frame_rate());
191 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> (decoder);
192 if (vd && _ignore_video) {
193 vd->set_ignore_video ();
196 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> (decoder);
197 if (ad && _ignore_audio) {
198 ad->set_ignore_audio ();
201 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc.get ())));
204 _have_valid_pieces = true;
208 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
210 shared_ptr<Content> c = w.lock ();
216 property == ContentProperty::POSITION ||
217 property == ContentProperty::LENGTH ||
218 property == ContentProperty::TRIM_START ||
219 property == ContentProperty::TRIM_END ||
220 property == ContentProperty::PATH ||
221 property == VideoContentProperty::VIDEO_FRAME_TYPE ||
222 property == DCPContentProperty::CAN_BE_PLAYED ||
223 property == TextSubtitleContentProperty::TEXT_SUBTITLE_COLOUR ||
224 property == TextSubtitleContentProperty::TEXT_SUBTITLE_OUTLINE ||
225 property == TextSubtitleContentProperty::TEXT_SUBTITLE_OUTLINE_COLOUR
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 (),
323 lrint (_video_container_size.width * i->rectangle.x),
324 lrint (_video_container_size.height * i->rectangle.y)
333 shared_ptr<PlayerVideo>
334 Player::black_player_video_frame (DCPTime time) const
336 return shared_ptr<PlayerVideo> (
338 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
342 _video_container_size,
343 _video_container_size,
346 PresetColourConversion::all().front().conversion
351 /** @return All PlayerVideos at the given time. There may be none if the content
352 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
353 * or 2 if we have 3D.
355 list<shared_ptr<PlayerVideo> >
356 Player::get_video (DCPTime time, bool accurate)
358 if (!_have_valid_pieces) {
362 /* Find subtitles for possible burn-in */
364 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
366 list<PositionImage> sub_images;
368 /* Image subtitles */
369 list<PositionImage> c = transform_image_subtitles (ps.image);
370 copy (c.begin(), c.end(), back_inserter (sub_images));
372 /* Text subtitles (rendered to an image) */
373 if (!ps.text.empty ()) {
374 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
375 copy (s.begin (), s.end (), back_inserter (sub_images));
378 optional<PositionImage> subtitles;
379 if (!sub_images.empty ()) {
380 subtitles = merge (sub_images);
383 /* Find pieces containing video which is happening now */
385 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
387 time + DCPTime::from_frames (1, _film->video_frame_rate ())
390 list<shared_ptr<PlayerVideo> > pvf;
393 /* No video content at this time */
394 pvf.push_back (black_player_video_frame (time));
396 /* Some video content at this time */
397 shared_ptr<Piece> last = *(ov.rbegin ());
398 VideoFrameType const last_type = dynamic_pointer_cast<VideoContent> (last->content)->video_frame_type ();
400 /* Get video from appropriate piece(s) */
401 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
403 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
404 DCPOMATIC_ASSERT (decoder);
405 shared_ptr<VideoContent> video_content = dynamic_pointer_cast<VideoContent> (piece->content);
406 DCPOMATIC_ASSERT (video_content);
408 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (video_content);
409 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
414 /* always use the last video */
416 /* with a corresponding L/R eye if appropriate */
417 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
418 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
421 /* We want to use this piece */
422 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
423 if (content_video.empty ()) {
424 pvf.push_back (black_player_video_frame (time));
426 dcp::Size image_size = video_content->scale().size (video_content, _video_container_size, _film->frame_size ());
428 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
430 shared_ptr<PlayerVideo> (
433 content_video_to_dcp (piece, i->frame),
434 video_content->crop (),
435 video_content->fade (i->frame),
437 _video_container_size,
440 video_content->colour_conversion ()
447 /* Discard unused video */
448 decoder->get_video (dcp_to_content_video (piece, time), accurate);
454 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
455 p->set_subtitle (subtitles.get ());
462 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
463 shared_ptr<AudioBuffers>
464 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
466 if (!_have_valid_pieces) {
470 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
472 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
473 audio->make_silent ();
475 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
480 bool all_referenced = true;
481 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
482 shared_ptr<AudioContent> audio_content = dynamic_pointer_cast<AudioContent> (i->content);
483 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
484 if (audio_content && (!dcp_content || !dcp_content->reference_audio ())) {
485 /* There is audio content which is not from a DCP or not set to be referenced */
486 all_referenced = false;
490 if (all_referenced && !_play_referenced) {
491 return shared_ptr<AudioBuffers> ();
494 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
496 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (i->content);
497 DCPOMATIC_ASSERT (content);
498 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> (i->decoder);
499 DCPOMATIC_ASSERT (decoder);
501 /* The time that we should request from the content */
502 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
503 Frame request_frames = length_frames;
505 if (request < DCPTime ()) {
506 /* We went off the start of the content, so we will need to offset
507 the stuff we get back.
510 request_frames += request.frames_round (_film->audio_frame_rate ());
511 if (request_frames < 0) {
514 request = DCPTime ();
517 Frame const content_frame = dcp_to_resampled_audio (i, request);
519 BOOST_FOREACH (AudioStreamPtr j, content->audio_streams ()) {
521 if (j->channels() == 0) {
522 /* Some content (e.g. DCPs) can have streams with no channels */
526 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
527 ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
530 if (content->audio_gain() != 0) {
531 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
532 gain->apply_gain (content->audio_gain ());
537 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
538 dcp_mapped->make_silent ();
539 AudioMapping map = j->mapping ();
540 for (int i = 0; i < map.input_channels(); ++i) {
541 for (int j = 0; j < _film->audio_channels(); ++j) {
542 if (map.get (i, j) > 0) {
543 dcp_mapped->accumulate_channel (
553 if (_audio_processor) {
554 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
557 all.audio = dcp_mapped;
559 audio->accumulate_frames (
561 content_frame - all.frame,
562 offset.frames_round (_film->audio_frame_rate()),
563 min (Frame (all.audio->frames()), request_frames)
572 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
574 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
575 DCPTime s = t - piece->content->position ();
576 s = min (piece->content->length_after_trim(), s);
577 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
579 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
580 then convert that ContentTime to frames at the content's rate. However this fails for
581 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
582 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
584 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
586 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
590 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
592 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
593 /* See comment in dcp_to_content_video */
594 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
595 return max (DCPTime (), d + piece->content->position ());
599 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
601 DCPTime s = t - piece->content->position ();
602 s = min (piece->content->length_after_trim(), s);
603 /* See notes in dcp_to_content_video */
604 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
608 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
610 DCPTime s = t - piece->content->position ();
611 s = min (piece->content->length_after_trim(), s);
612 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
616 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
618 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
621 /** @param burnt true to return only subtitles to be burnt, false to return only
622 * subtitles that should not be burnt. This parameter will be ignored if
623 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
626 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
628 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
630 PlayerSubtitles ps (time, length);
632 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
633 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
634 if (!subtitle_content->use_subtitles () || (!_always_burn_subtitles && (burnt != subtitle_content->burn_subtitles ()))) {
638 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (subtitle_content);
639 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
643 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder);
644 ContentTime const from = dcp_to_content_subtitle (*j, time);
645 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
646 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
648 list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting, accurate);
649 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
651 /* Apply content's subtitle offsets */
652 i->sub.rectangle.x += subtitle_content->subtitle_x_offset ();
653 i->sub.rectangle.y += subtitle_content->subtitle_y_offset ();
655 /* Apply content's subtitle scale */
656 i->sub.rectangle.width *= subtitle_content->subtitle_x_scale ();
657 i->sub.rectangle.height *= subtitle_content->subtitle_y_scale ();
659 /* Apply a corrective translation to keep the subtitle centred after that scale */
660 i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1);
661 i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1);
663 ps.image.push_back (i->sub);
666 list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting, accurate);
667 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
668 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
669 s.set_h_position (s.h_position() + subtitle_content->subtitle_x_offset ());
670 s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ());
671 float const xs = subtitle_content->subtitle_x_scale();
672 float const ys = subtitle_content->subtitle_y_scale();
673 float size = s.size();
675 /* Adjust size to express the common part of the scaling;
676 e.g. if xs = ys = 0.5 we scale size by 2.
678 if (xs > 1e-5 && ys > 1e-5) {
679 size *= 1 / min (1 / xs, 1 / ys);
683 /* Then express aspect ratio changes */
684 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
685 s.set_aspect_adjust (xs / ys);
687 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
688 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
689 ps.text.push_back (s);
690 ps.add_fonts (subtitle_content->fonts ());
698 list<shared_ptr<Font> >
699 Player::get_subtitle_fonts ()
701 if (!_have_valid_pieces) {
705 list<shared_ptr<Font> > fonts;
706 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
707 shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (p->content);
709 /* XXX: things may go wrong if there are duplicate font IDs
710 with different font files.
712 list<shared_ptr<Font> > f = sc->fonts ();
713 copy (f.begin(), f.end(), back_inserter (fonts));
720 /** Set this player never to produce any video data */
722 Player::set_ignore_video ()
724 _ignore_video = true;
727 /** Set this player never to produce any audio data */
729 Player::set_ignore_audio ()
731 _ignore_audio = true;
734 /** Set whether or not this player should always burn text subtitles into the image,
735 * regardless of the content settings.
736 * @param burn true to always burn subtitles, false to obey content settings.
739 Player::set_always_burn_subtitles (bool burn)
741 _always_burn_subtitles = burn;
748 _have_valid_pieces = false;
752 Player::set_play_referenced ()
754 _play_referenced = true;
755 _have_valid_pieces = false;
758 list<ReferencedReelAsset>
759 Player::get_reel_assets ()
761 list<ReferencedReelAsset> a;
763 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
764 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
769 scoped_ptr<DCPDecoder> decoder;
771 decoder.reset (new DCPDecoder (j, false));
777 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
778 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
779 if (j->reference_video ()) {
781 ReferencedReelAsset (
783 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
788 if (j->reference_audio ()) {
790 ReferencedReelAsset (
792 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
797 if (j->reference_subtitle ()) {
798 DCPOMATIC_ASSERT (k->main_subtitle ());
800 ReferencedReelAsset (
802 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
807 /* Assume that main picture duration is the length of the reel */
808 offset += k->main_picture()->duration ();