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 "subrip_decoder.h"
31 #include "subrip_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).
178 shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (i);
180 decoder.reset (new SubRipDecoder (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
225 _have_valid_pieces = false;
229 property == SubtitleContentProperty::USE_SUBTITLES ||
230 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
231 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
232 property == SubtitleContentProperty::SUBTITLE_X_SCALE ||
233 property == SubtitleContentProperty::SUBTITLE_Y_SCALE ||
234 property == SubtitleContentProperty::FONTS ||
235 property == VideoContentProperty::VIDEO_CROP ||
236 property == VideoContentProperty::VIDEO_SCALE ||
237 property == VideoContentProperty::VIDEO_FRAME_RATE ||
238 property == VideoContentProperty::VIDEO_FADE_IN ||
239 property == VideoContentProperty::VIDEO_FADE_OUT ||
240 property == VideoContentProperty::COLOUR_CONVERSION
248 Player::set_video_container_size (dcp::Size s)
250 _video_container_size = s;
252 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
253 _black_image->make_black ();
257 Player::playlist_changed ()
259 _have_valid_pieces = false;
264 Player::film_changed (Film::Property p)
266 /* Here we should notice Film properties that affect our output, and
267 alert listeners that our output now would be different to how it was
268 last time we were run.
271 if (p == Film::CONTAINER) {
273 } else if (p == Film::VIDEO_FRAME_RATE) {
274 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
275 so we need new pieces here.
277 _have_valid_pieces = false;
279 } else if (p == Film::AUDIO_PROCESSOR) {
280 if (_film->audio_processor ()) {
281 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
287 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
289 list<PositionImage> all;
291 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
296 /* We will scale the subtitle up to fit _video_container_size */
297 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
299 /* Then we need a corrective translation, consisting of two parts:
301 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
302 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
304 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
305 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
306 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
308 * Combining these two translations gives these expressions.
315 dcp::YUV_TO_RGB_REC601,
316 i->image->pixel_format (),
320 lrint (_video_container_size.width * i->rectangle.x),
321 lrint (_video_container_size.height * i->rectangle.y)
330 shared_ptr<PlayerVideo>
331 Player::black_player_video_frame (DCPTime time) const
333 return shared_ptr<PlayerVideo> (
335 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
339 _video_container_size,
340 _video_container_size,
343 PresetColourConversion::all().front().conversion
348 /** @return All PlayerVideos at the given time. There may be none if the content
349 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
350 * or 2 if we have 3D.
352 list<shared_ptr<PlayerVideo> >
353 Player::get_video (DCPTime time, bool accurate)
355 if (!_have_valid_pieces) {
359 /* Find subtitles for possible burn-in */
361 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
363 list<PositionImage> sub_images;
365 /* Image subtitles */
366 list<PositionImage> c = transform_image_subtitles (ps.image);
367 copy (c.begin(), c.end(), back_inserter (sub_images));
369 /* Text subtitles (rendered to an image) */
370 if (!ps.text.empty ()) {
371 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
372 copy (s.begin (), s.end (), back_inserter (sub_images));
375 optional<PositionImage> subtitles;
376 if (!sub_images.empty ()) {
377 subtitles = merge (sub_images);
380 /* Find pieces containing video which is happening now */
382 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
384 time + DCPTime::from_frames (1, _film->video_frame_rate ())
387 list<shared_ptr<PlayerVideo> > pvf;
390 /* No video content at this time */
391 pvf.push_back (black_player_video_frame (time));
393 /* Some video content at this time */
394 shared_ptr<Piece> last = *(ov.rbegin ());
395 VideoFrameType const last_type = dynamic_pointer_cast<VideoContent> (last->content)->video_frame_type ();
397 /* Get video from appropriate piece(s) */
398 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
400 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
401 DCPOMATIC_ASSERT (decoder);
402 shared_ptr<VideoContent> video_content = dynamic_pointer_cast<VideoContent> (piece->content);
403 DCPOMATIC_ASSERT (video_content);
405 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (video_content);
406 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
411 /* always use the last video */
413 /* with a corresponding L/R eye if appropriate */
414 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
415 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
418 /* We want to use this piece */
419 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
420 if (content_video.empty ()) {
421 pvf.push_back (black_player_video_frame (time));
423 dcp::Size image_size = video_content->scale().size (video_content, _video_container_size, _film->frame_size ());
425 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
427 shared_ptr<PlayerVideo> (
430 content_video_to_dcp (piece, i->frame),
431 video_content->crop (),
432 video_content->fade (i->frame),
434 _video_container_size,
437 video_content->colour_conversion ()
444 /* Discard unused video */
445 decoder->get_video (dcp_to_content_video (piece, time), accurate);
451 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
452 p->set_subtitle (subtitles.get ());
459 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
460 shared_ptr<AudioBuffers>
461 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
463 if (!_have_valid_pieces) {
467 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
469 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
470 audio->make_silent ();
472 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
477 bool all_referenced = true;
478 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
479 shared_ptr<AudioContent> audio_content = dynamic_pointer_cast<AudioContent> (i->content);
480 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
481 if (audio_content && (!dcp_content || !dcp_content->reference_audio ())) {
482 /* There is audio content which is not from a DCP or not set to be referenced */
483 all_referenced = false;
487 if (all_referenced && !_play_referenced) {
488 return shared_ptr<AudioBuffers> ();
491 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
493 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (i->content);
494 DCPOMATIC_ASSERT (content);
495 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> (i->decoder);
496 DCPOMATIC_ASSERT (decoder);
498 /* The time that we should request from the content */
499 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
500 Frame request_frames = length_frames;
502 if (request < DCPTime ()) {
503 /* We went off the start of the content, so we will need to offset
504 the stuff we get back.
507 request_frames += request.frames_round (_film->audio_frame_rate ());
508 if (request_frames < 0) {
511 request = DCPTime ();
514 Frame const content_frame = dcp_to_resampled_audio (i, request);
516 BOOST_FOREACH (AudioStreamPtr j, content->audio_streams ()) {
518 if (j->channels() == 0) {
519 /* Some content (e.g. DCPs) can have streams with no channels */
523 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
524 ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
527 if (content->audio_gain() != 0) {
528 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
529 gain->apply_gain (content->audio_gain ());
534 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
535 dcp_mapped->make_silent ();
536 AudioMapping map = j->mapping ();
537 for (int i = 0; i < map.input_channels(); ++i) {
538 for (int j = 0; j < _film->audio_channels(); ++j) {
539 if (map.get (i, j) > 0) {
540 dcp_mapped->accumulate_channel (
550 if (_audio_processor) {
551 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
554 all.audio = dcp_mapped;
556 audio->accumulate_frames (
558 content_frame - all.frame,
559 offset.frames_round (_film->audio_frame_rate()),
560 min (Frame (all.audio->frames()), request_frames)
569 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
571 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
572 DCPTime s = t - piece->content->position ();
573 s = min (piece->content->length_after_trim(), s);
574 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
576 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
577 then convert that ContentTime to frames at the content's rate. However this fails for
578 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
579 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
581 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
583 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
587 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
589 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
590 /* See comment in dcp_to_content_video */
591 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
592 return max (DCPTime (), d + piece->content->position ());
596 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
598 DCPTime s = t - piece->content->position ();
599 s = min (piece->content->length_after_trim(), s);
600 /* See notes in dcp_to_content_video */
601 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
605 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
607 DCPTime s = t - piece->content->position ();
608 s = min (piece->content->length_after_trim(), s);
609 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
613 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
615 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
618 /** @param burnt true to return only subtitles to be burnt, false to return only
619 * subtitles that should not be burnt. This parameter will be ignored if
620 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
623 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
625 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
627 PlayerSubtitles ps (time, length);
629 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
630 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
631 if (!subtitle_content->use_subtitles () || (!_always_burn_subtitles && (burnt != subtitle_content->burn_subtitles ()))) {
635 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (subtitle_content);
636 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
640 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder);
641 ContentTime const from = dcp_to_content_subtitle (*j, time);
642 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
643 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
645 list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting, accurate);
646 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
648 /* Apply content's subtitle offsets */
649 i->sub.rectangle.x += subtitle_content->subtitle_x_offset ();
650 i->sub.rectangle.y += subtitle_content->subtitle_y_offset ();
652 /* Apply content's subtitle scale */
653 i->sub.rectangle.width *= subtitle_content->subtitle_x_scale ();
654 i->sub.rectangle.height *= subtitle_content->subtitle_y_scale ();
656 /* Apply a corrective translation to keep the subtitle centred after that scale */
657 i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1);
658 i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1);
660 ps.image.push_back (i->sub);
663 list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting, accurate);
664 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
665 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
666 s.set_h_position (s.h_position() + subtitle_content->subtitle_x_offset ());
667 s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ());
668 float const xs = subtitle_content->subtitle_x_scale();
669 float const ys = subtitle_content->subtitle_y_scale();
670 float const average = s.size() * (xs + ys) / 2;
671 s.set_size (average);
672 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
673 s.set_aspect_adjust (xs / ys);
675 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
676 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
677 ps.text.push_back (s);
678 ps.add_fonts (subtitle_content->fonts ());
686 list<shared_ptr<Font> >
687 Player::get_subtitle_fonts ()
689 if (!_have_valid_pieces) {
693 list<shared_ptr<Font> > fonts;
694 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
695 shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (p->content);
697 /* XXX: things may go wrong if there are duplicate font IDs
698 with different font files.
700 list<shared_ptr<Font> > f = sc->fonts ();
701 copy (f.begin(), f.end(), back_inserter (fonts));
708 /** Set this player never to produce any video data */
710 Player::set_ignore_video ()
712 _ignore_video = true;
715 /** Set this player never to produce any audio data */
717 Player::set_ignore_audio ()
719 _ignore_audio = true;
722 /** Set whether or not this player should always burn text subtitles into the image,
723 * regardless of the content settings.
724 * @param burn true to always burn subtitles, false to obey content settings.
727 Player::set_always_burn_subtitles (bool burn)
729 _always_burn_subtitles = burn;
736 _have_valid_pieces = false;
740 Player::set_play_referenced ()
742 _play_referenced = true;
743 _have_valid_pieces = false;
746 list<ReferencedReelAsset>
747 Player::get_reel_assets ()
749 list<ReferencedReelAsset> a;
751 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
752 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
757 scoped_ptr<DCPDecoder> decoder;
759 decoder.reset (new DCPDecoder (j, false));
765 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
766 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
767 if (j->reference_video ()) {
769 ReferencedReelAsset (
771 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
776 if (j->reference_audio ()) {
778 ReferencedReelAsset (
780 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
785 if (j->reference_subtitle ()) {
786 DCPOMATIC_ASSERT (k->main_subtitle ());
788 ReferencedReelAsset (
790 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
795 /* Assume that main picture duration is the length of the reel */
796 offset += k->main_picture()->duration ();