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;
80 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
82 , _playlist (playlist)
83 , _have_valid_pieces (false)
84 , _ignore_video (false)
85 , _ignore_audio (false)
86 , _always_burn_subtitles (false)
88 , _play_referenced (false)
90 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
91 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
92 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
93 set_video_container_size (_film->frame_size ());
95 film_changed (Film::AUDIO_PROCESSOR);
99 Player::setup_pieces ()
101 list<shared_ptr<Piece> > old_pieces = _pieces;
104 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
106 if (!i->paths_valid ()) {
110 shared_ptr<Decoder> decoder;
111 optional<FrameRateChange> frc;
114 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i);
116 decoder.reset (new FFmpegDecoder (fc, _film->log(), _fast));
117 frc = FrameRateChange (fc->video->frame_rate(), _film->video_frame_rate());
120 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
122 decoder.reset (new DCPDecoder (dc, _film->log(), _fast));
123 frc = FrameRateChange (dc->video->frame_rate(), _film->video_frame_rate());
127 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
129 /* See if we can re-use an old ImageDecoder */
130 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
131 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
132 if (imd && imd->content() == ic) {
138 decoder.reset (new ImageDecoder (ic, _film->log()));
141 frc = FrameRateChange (ic->video->frame_rate(), _film->video_frame_rate());
145 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i);
147 decoder.reset (new SndfileDecoder (sc, _fast, _film->log()));
149 /* Work out a FrameRateChange for the best overlap video for this content */
150 DCPTime best_overlap_t;
151 shared_ptr<Content> best_overlap;
152 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
157 DCPTime const overlap = min (j->end(), i->end()) - max (j->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::FRAME_TYPE ||
222 property == DCPContentProperty::CAN_BE_PLAYED ||
223 property == SubtitleContentProperty::COLOUR ||
224 property == SubtitleContentProperty::OUTLINE ||
225 property == SubtitleContentProperty::OUTLINE_COLOUR ||
226 property == FFmpegContentProperty::SUBTITLE_STREAM
229 _have_valid_pieces = false;
233 property == SubtitleContentProperty::USE ||
234 property == SubtitleContentProperty::X_OFFSET ||
235 property == SubtitleContentProperty::Y_OFFSET ||
236 property == SubtitleContentProperty::X_SCALE ||
237 property == SubtitleContentProperty::Y_SCALE ||
238 property == SubtitleContentProperty::FONTS ||
239 property == VideoContentProperty::CROP ||
240 property == VideoContentProperty::SCALE ||
241 property == VideoContentProperty::FRAME_RATE ||
242 property == VideoContentProperty::FADE_IN ||
243 property == VideoContentProperty::FADE_OUT ||
244 property == VideoContentProperty::COLOUR_CONVERSION
252 Player::set_video_container_size (dcp::Size s)
254 _video_container_size = s;
256 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
257 _black_image->make_black ();
261 Player::playlist_changed ()
263 _have_valid_pieces = false;
268 Player::film_changed (Film::Property p)
270 /* Here we should notice Film properties that affect our output, and
271 alert listeners that our output now would be different to how it was
272 last time we were run.
275 if (p == Film::CONTAINER) {
277 } else if (p == Film::VIDEO_FRAME_RATE) {
278 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
279 so we need new pieces here.
281 _have_valid_pieces = false;
283 } else if (p == Film::AUDIO_PROCESSOR) {
284 if (_film->audio_processor ()) {
285 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
291 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
293 list<PositionImage> all;
295 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
300 /* We will scale the subtitle up to fit _video_container_size */
301 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
303 /* Then we need a corrective translation, consisting of two parts:
305 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
306 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
308 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
309 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
310 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
312 * Combining these two translations gives these expressions.
319 dcp::YUV_TO_RGB_REC601,
320 i->image->pixel_format (),
325 lrint (_video_container_size.width * i->rectangle.x),
326 lrint (_video_container_size.height * i->rectangle.y)
335 shared_ptr<PlayerVideo>
336 Player::black_player_video_frame (DCPTime time) const
338 return shared_ptr<PlayerVideo> (
340 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
344 _video_container_size,
345 _video_container_size,
348 PresetColourConversion::all().front().conversion
353 /** @return All PlayerVideos at the given time. There may be none if the content
354 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
355 * or 2 if we have 3D.
357 list<shared_ptr<PlayerVideo> >
358 Player::get_video (DCPTime time, bool accurate)
360 if (!_have_valid_pieces) {
364 /* Find subtitles for possible burn-in */
366 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
368 list<PositionImage> sub_images;
370 /* Image subtitles */
371 list<PositionImage> c = transform_image_subtitles (ps.image);
372 copy (c.begin(), c.end(), back_inserter (sub_images));
374 /* Text subtitles (rendered to an image) */
375 if (!ps.text.empty ()) {
376 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
377 copy (s.begin (), s.end (), back_inserter (sub_images));
380 optional<PositionImage> subtitles;
381 if (!sub_images.empty ()) {
382 subtitles = merge (sub_images);
385 /* Find pieces containing video which is happening now */
387 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
389 time + DCPTime::from_frames (1, _film->video_frame_rate ())
392 list<shared_ptr<PlayerVideo> > pvf;
395 /* No video content at this time */
396 pvf.push_back (black_player_video_frame (time));
398 /* Some video content at this time */
399 shared_ptr<Piece> last = *(ov.rbegin ());
400 VideoFrameType const last_type = last->content->video->frame_type ();
402 /* Get video from appropriate piece(s) */
403 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
405 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
406 DCPOMATIC_ASSERT (decoder);
408 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->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 && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
418 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->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 = piece->content->video->scale().size (
427 piece->content->video, _video_container_size, _film->frame_size ()
430 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
432 shared_ptr<PlayerVideo> (
435 content_video_to_dcp (piece, i->frame),
436 piece->content->video->crop (),
437 piece->content->video->fade (i->frame),
439 _video_container_size,
442 piece->content->video->colour_conversion ()
449 /* Discard unused video */
450 decoder->get_video (dcp_to_content_video (piece, time), accurate);
456 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
457 p->set_subtitle (subtitles.get ());
464 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
465 shared_ptr<AudioBuffers>
466 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
468 if (!_have_valid_pieces) {
472 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
474 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
475 audio->make_silent ();
477 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
482 bool all_referenced = true;
483 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
484 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
485 if (i->content->audio && (!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 DCPOMATIC_ASSERT (i->content->audio);
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 (i->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, i->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 (i->content->audio->gain() != 0) {
531 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
532 gain->apply_gain (i->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 DCPTime s = t - piece->content->position ();
575 s = min (piece->content->length_after_trim(), s);
576 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
578 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
579 then convert that ContentTime to frames at the content's rate. However this fails for
580 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
581 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
583 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
585 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
589 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
591 /* See comment in dcp_to_content_video */
592 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
593 return max (DCPTime (), d + piece->content->position ());
597 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
599 DCPTime s = t - piece->content->position ();
600 s = min (piece->content->length_after_trim(), s);
601 /* See notes in dcp_to_content_video */
602 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
606 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
608 DCPTime s = t - piece->content->position ();
609 s = min (piece->content->length_after_trim(), s);
610 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
614 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
616 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
619 /** @param burnt true to return only subtitles to be burnt, false to return only
620 * subtitles that should not be burnt. This parameter will be ignored if
621 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
624 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
626 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
628 PlayerSubtitles ps (time, length);
630 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
631 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
635 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->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 += (*j)->content->subtitle->x_offset ();
650 i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
652 /* Apply content's subtitle scale */
653 i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
654 i->sub.rectangle.height *= (*j)->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 * ((*j)->content->subtitle->x_scale() - 1);
658 i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->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() + (*j)->content->subtitle->x_offset ());
667 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
668 float const xs = (*j)->content->subtitle->x_scale();
669 float const ys = (*j)->content->subtitle->y_scale();
670 float size = s.size();
672 /* Adjust size to express the common part of the scaling;
673 e.g. if xs = ys = 0.5 we scale size by 2.
675 if (xs > 1e-5 && ys > 1e-5) {
676 size *= 1 / min (1 / xs, 1 / ys);
680 /* Then express aspect ratio changes */
681 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
682 s.set_aspect_adjust (xs / ys);
684 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
685 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
686 ps.text.push_back (s);
687 ps.add_fonts ((*j)->content->subtitle->fonts ());
695 list<shared_ptr<Font> >
696 Player::get_subtitle_fonts ()
698 if (!_have_valid_pieces) {
702 list<shared_ptr<Font> > fonts;
703 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
704 if (p->content->subtitle) {
705 /* XXX: things may go wrong if there are duplicate font IDs
706 with different font files.
708 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
709 copy (f.begin(), f.end(), back_inserter (fonts));
716 /** Set this player never to produce any video data */
718 Player::set_ignore_video ()
720 _ignore_video = true;
723 /** Set this player never to produce any audio data */
725 Player::set_ignore_audio ()
727 _ignore_audio = true;
730 /** Set whether or not this player should always burn text subtitles into the image,
731 * regardless of the content settings.
732 * @param burn true to always burn subtitles, false to obey content settings.
735 Player::set_always_burn_subtitles (bool burn)
737 _always_burn_subtitles = burn;
744 _have_valid_pieces = false;
748 Player::set_play_referenced ()
750 _play_referenced = true;
751 _have_valid_pieces = false;
754 list<ReferencedReelAsset>
755 Player::get_reel_assets ()
757 list<ReferencedReelAsset> a;
759 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
760 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
765 scoped_ptr<DCPDecoder> decoder;
767 decoder.reset (new DCPDecoder (j, _film->log(), false));
773 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
774 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
775 if (j->reference_video ()) {
777 ReferencedReelAsset (
779 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
784 if (j->reference_audio ()) {
786 ReferencedReelAsset (
788 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
793 if (j->reference_subtitle ()) {
794 DCPOMATIC_ASSERT (k->main_subtitle ());
796 ReferencedReelAsset (
798 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
803 /* Assume that main picture duration is the length of the reel */
804 offset += k->main_picture()->duration ();