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;
78 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
80 , _playlist (playlist)
81 , _have_valid_pieces (false)
82 , _ignore_video (false)
83 , _ignore_audio (false)
84 , _always_burn_subtitles (false)
86 , _play_referenced (false)
88 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
89 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
90 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
91 set_video_container_size (_film->frame_size ());
93 film_changed (Film::AUDIO_PROCESSOR);
97 Player::setup_pieces ()
99 list<shared_ptr<Piece> > old_pieces = _pieces;
102 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
104 if (!i->paths_valid ()) {
108 shared_ptr<Decoder> decoder;
109 optional<FrameRateChange> frc;
112 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i);
114 decoder.reset (new FFmpegDecoder (fc, _film->log(), _fast));
115 frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
118 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
120 decoder.reset (new DCPDecoder (dc, _fast));
121 frc = FrameRateChange (dc->video_frame_rate(), _film->video_frame_rate());
125 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
127 /* See if we can re-use an old ImageDecoder */
128 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
129 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
130 if (imd && imd->content() == ic) {
136 decoder.reset (new ImageDecoder (ic));
139 frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
143 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i);
145 decoder.reset (new SndfileDecoder (sc, _fast));
147 /* Work out a FrameRateChange for the best overlap video for this content */
148 DCPTime best_overlap_t;
149 shared_ptr<VideoContent> best_overlap;
150 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
151 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (j);
156 DCPTime const overlap = min (vc->end(), i->end()) - max (vc->position(), i->position());
157 if (overlap > best_overlap_t) {
159 best_overlap_t = overlap;
164 frc = FrameRateChange (best_overlap->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).
177 shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (i);
179 decoder.reset (new SubRipDecoder (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
224 _have_valid_pieces = false;
228 property == SubtitleContentProperty::USE_SUBTITLES ||
229 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
230 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
231 property == SubtitleContentProperty::SUBTITLE_X_SCALE ||
232 property == SubtitleContentProperty::SUBTITLE_Y_SCALE ||
233 property == SubtitleContentProperty::FONTS ||
234 property == VideoContentProperty::VIDEO_CROP ||
235 property == VideoContentProperty::VIDEO_SCALE ||
236 property == VideoContentProperty::VIDEO_FRAME_RATE ||
237 property == VideoContentProperty::VIDEO_FADE_IN ||
238 property == VideoContentProperty::VIDEO_FADE_OUT ||
239 property == VideoContentProperty::COLOUR_CONVERSION
247 Player::set_video_container_size (dcp::Size s)
249 _video_container_size = s;
251 _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
252 _black_image->make_black ();
256 Player::playlist_changed ()
258 _have_valid_pieces = false;
263 Player::film_changed (Film::Property p)
265 /* Here we should notice Film properties that affect our output, and
266 alert listeners that our output now would be different to how it was
267 last time we were run.
270 if (p == Film::CONTAINER) {
272 } else if (p == Film::VIDEO_FRAME_RATE) {
273 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
274 so we need new pieces here.
276 _have_valid_pieces = false;
278 } else if (p == Film::AUDIO_PROCESSOR) {
279 if (_film->audio_processor ()) {
280 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
286 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
288 list<PositionImage> all;
290 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
295 /* We will scale the subtitle up to fit _video_container_size */
296 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
298 /* Then we need a corrective translation, consisting of two parts:
300 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
301 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
303 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
304 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
305 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
307 * Combining these two translations gives these expressions.
314 dcp::YUV_TO_RGB_REC601,
315 i->image->pixel_format (),
319 lrint (_video_container_size.width * i->rectangle.x),
320 lrint (_video_container_size.height * i->rectangle.y)
329 shared_ptr<PlayerVideo>
330 Player::black_player_video_frame (DCPTime time) const
332 return shared_ptr<PlayerVideo> (
334 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
338 _video_container_size,
339 _video_container_size,
342 PresetColourConversion::all().front().conversion
347 /** @return All PlayerVideos at the given time. There may be none if the content
348 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
349 * or 2 if we have 3D.
351 list<shared_ptr<PlayerVideo> >
352 Player::get_video (DCPTime time, bool accurate)
354 if (!_have_valid_pieces) {
358 /* Find subtitles for possible burn-in */
360 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true);
362 list<PositionImage> sub_images;
364 /* Image subtitles */
365 list<PositionImage> c = transform_image_subtitles (ps.image);
366 copy (c.begin(), c.end(), back_inserter (sub_images));
368 /* Text subtitles (rendered to an image) */
369 if (!ps.text.empty ()) {
370 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
371 copy (s.begin (), s.end (), back_inserter (sub_images));
374 optional<PositionImage> subtitles;
375 if (!sub_images.empty ()) {
376 subtitles = merge (sub_images);
379 /* Find pieces containing video which is happening now */
381 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
383 time + DCPTime::from_frames (1, _film->video_frame_rate ()) - DCPTime::delta()
386 list<shared_ptr<PlayerVideo> > pvf;
389 /* No video content at this time */
390 pvf.push_back (black_player_video_frame (time));
392 /* Some video content at this time */
393 shared_ptr<Piece> last = *(ov.rbegin ());
394 VideoFrameType const last_type = dynamic_pointer_cast<VideoContent> (last->content)->video_frame_type ();
396 /* Get video from appropriate piece(s) */
397 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
399 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
400 DCPOMATIC_ASSERT (decoder);
401 shared_ptr<VideoContent> video_content = dynamic_pointer_cast<VideoContent> (piece->content);
402 DCPOMATIC_ASSERT (video_content);
404 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (video_content);
405 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
410 /* always use the last video */
412 /* with a corresponding L/R eye if appropriate */
413 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
414 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && video_content->video_frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
417 /* We want to use this piece */
418 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
419 if (content_video.empty ()) {
420 pvf.push_back (black_player_video_frame (time));
422 dcp::Size image_size = video_content->scale().size (video_content, _video_container_size, _film->frame_size ());
424 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
426 shared_ptr<PlayerVideo> (
429 content_video_to_dcp (piece, i->frame),
430 video_content->crop (),
431 video_content->fade (i->frame),
433 _video_container_size,
436 video_content->colour_conversion ()
443 /* Discard unused video */
444 decoder->get_video (dcp_to_content_video (piece, time), accurate);
450 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
451 p->set_subtitle (subtitles.get ());
458 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
459 shared_ptr<AudioBuffers>
460 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
462 if (!_have_valid_pieces) {
466 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
468 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
469 audio->make_silent ();
471 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
476 bool all_referenced = true;
477 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
478 shared_ptr<AudioContent> audio_content = dynamic_pointer_cast<AudioContent> (i->content);
479 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
480 if (audio_content && (!dcp_content || !dcp_content->reference_audio ())) {
481 /* There is audio content which is not from a DCP or not set to be referenced */
482 all_referenced = false;
486 if (all_referenced && !_play_referenced) {
487 return shared_ptr<AudioBuffers> ();
490 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
492 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (i->content);
493 DCPOMATIC_ASSERT (content);
494 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> (i->decoder);
495 DCPOMATIC_ASSERT (decoder);
497 /* The time that we should request from the content */
498 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
499 Frame request_frames = length_frames;
501 if (request < DCPTime ()) {
502 /* We went off the start of the content, so we will need to offset
503 the stuff we get back.
506 request_frames += request.frames_round (_film->audio_frame_rate ());
507 if (request_frames < 0) {
510 request = DCPTime ();
513 Frame const content_frame = dcp_to_resampled_audio (i, request);
515 BOOST_FOREACH (AudioStreamPtr j, content->audio_streams ()) {
517 if (j->channels() == 0) {
518 /* Some content (e.g. DCPs) can have streams with no channels */
522 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
523 ContentAudio all = decoder->get_audio (j, content_frame, request_frames, accurate);
526 if (content->audio_gain() != 0) {
527 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
528 gain->apply_gain (content->audio_gain ());
533 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
534 dcp_mapped->make_silent ();
535 AudioMapping map = j->mapping ();
536 for (int i = 0; i < map.input_channels(); ++i) {
537 for (int j = 0; j < _film->audio_channels(); ++j) {
538 if (map.get (i, j) > 0) {
539 dcp_mapped->accumulate_channel (
549 if (_audio_processor) {
550 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
553 all.audio = dcp_mapped;
555 audio->accumulate_frames (
557 content_frame - all.frame,
558 offset.frames_round (_film->audio_frame_rate()),
559 min (Frame (all.audio->frames()), request_frames)
568 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
570 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
571 DCPTime s = t - piece->content->position ();
572 s = min (piece->content->length_after_trim(), s);
573 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
575 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
576 then convert that ContentTime to frames at the content's rate. However this fails for
577 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
578 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
580 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
582 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
586 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
588 shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (piece->content);
589 /* See comment in dcp_to_content_video */
590 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
591 return max (DCPTime (), d + piece->content->position ());
595 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
597 DCPTime s = t - piece->content->position ();
598 s = min (piece->content->length_after_trim(), s);
599 /* See notes in dcp_to_content_video */
600 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
604 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
606 DCPTime s = t - piece->content->position ();
607 s = min (piece->content->length_after_trim(), s);
608 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
612 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
614 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
617 /** @param burnt true to return only subtitles to be burnt, false to return only
618 * subtitles that should not be burnt. This parameter will be ignored if
619 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
622 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt)
624 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
626 PlayerSubtitles ps (time, length);
628 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
629 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
630 if (!subtitle_content->use_subtitles () || (!_always_burn_subtitles && (burnt != subtitle_content->burn_subtitles ()))) {
634 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (subtitle_content);
635 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
639 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*j)->decoder);
640 ContentTime const from = dcp_to_content_subtitle (*j, time);
641 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
642 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
644 list<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting);
645 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
647 /* Apply content's subtitle offsets */
648 i->sub.rectangle.x += subtitle_content->subtitle_x_offset ();
649 i->sub.rectangle.y += subtitle_content->subtitle_y_offset ();
651 /* Apply content's subtitle scale */
652 i->sub.rectangle.width *= subtitle_content->subtitle_x_scale ();
653 i->sub.rectangle.height *= subtitle_content->subtitle_y_scale ();
655 /* Apply a corrective translation to keep the subtitle centred after that scale */
656 i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1);
657 i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1);
659 ps.image.push_back (i->sub);
662 list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting);
663 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
664 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
665 s.set_h_position (s.h_position() + subtitle_content->subtitle_x_offset ());
666 s.set_v_position (s.v_position() + subtitle_content->subtitle_y_offset ());
667 float const xs = subtitle_content->subtitle_x_scale();
668 float const ys = subtitle_content->subtitle_y_scale();
669 float const average = s.size() * (xs + ys) / 2;
670 s.set_size (average);
671 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
672 s.set_aspect_adjust (xs / ys);
674 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds()));
675 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds()));
676 ps.text.push_back (s);
677 ps.add_fonts (subtitle_content->fonts ());
685 list<shared_ptr<Font> >
686 Player::get_subtitle_fonts ()
688 if (!_have_valid_pieces) {
692 list<shared_ptr<Font> > fonts;
693 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
694 shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (p->content);
696 /* XXX: things may go wrong if there are duplicate font IDs
697 with different font files.
699 list<shared_ptr<Font> > f = sc->fonts ();
700 copy (f.begin(), f.end(), back_inserter (fonts));
707 /** Set this player never to produce any video data */
709 Player::set_ignore_video ()
711 _ignore_video = true;
714 /** Set this player never to produce any audio data */
716 Player::set_ignore_audio ()
718 _ignore_audio = true;
721 /** Set whether or not this player should always burn text subtitles into the image,
722 * regardless of the content settings.
723 * @param burn true to always burn subtitles, false to obey content settings.
726 Player::set_always_burn_subtitles (bool burn)
728 _always_burn_subtitles = burn;
735 _have_valid_pieces = false;
739 Player::set_play_referenced ()
741 _play_referenced = true;
742 _have_valid_pieces = false;
745 list<ReferencedReelAsset>
746 Player::get_reel_assets ()
748 list<ReferencedReelAsset> a;
750 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
751 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
755 DCPDecoder decoder (j, false);
757 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder.reels()) {
758 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
759 if (j->reference_video ()) {
761 ReferencedReelAsset (
763 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
768 if (j->reference_audio ()) {
770 ReferencedReelAsset (
772 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
777 if (j->reference_subtitle ()) {
779 ReferencedReelAsset (
781 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
786 /* Assume that main picture duration is the length of the reel */
787 offset += k->main_picture()->duration ();