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 "video_decoder.h"
24 #include "audio_buffers.h"
25 #include "audio_content.h"
26 #include "ffmpeg_content.h"
27 #include "image_decoder.h"
28 #include "image_content.h"
29 #include "sndfile_decoder.h"
30 #include "sndfile_content.h"
31 #include "subtitle_content.h"
32 #include "text_subtitle_decoder.h"
33 #include "text_subtitle_content.h"
34 #include "dcp_content.h"
37 #include "raw_image_proxy.h"
40 #include "render_subtitles.h"
42 #include "content_video.h"
43 #include "player_video.h"
44 #include "frame_rate_change.h"
45 #include "dcp_content.h"
46 #include "dcp_decoder.h"
47 #include "dcp_subtitle_content.h"
48 #include "dcp_subtitle_decoder.h"
49 #include "audio_processor.h"
51 #include "referenced_reel_asset.h"
53 #include <dcp/reel_sound_asset.h>
54 #include <dcp/reel_subtitle_asset.h>
55 #include <dcp/reel_picture_asset.h>
56 #include <boost/foreach.hpp>
63 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
75 using boost::shared_ptr;
76 using boost::weak_ptr;
77 using boost::dynamic_pointer_cast;
78 using boost::optional;
79 using boost::scoped_ptr;
82 has_video (Content* c)
84 return static_cast<bool>(c->video);
88 has_audio (Content* c)
90 return static_cast<bool>(c->audio);
94 has_subtitle (Content* c)
96 return static_cast<bool>(c->subtitle);
99 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
101 , _playlist (playlist)
102 , _have_valid_pieces (false)
103 , _ignore_video (false)
104 , _ignore_audio (false)
105 , _always_burn_subtitles (false)
107 , _play_referenced (false)
109 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
110 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
111 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
112 set_video_container_size (_film->frame_size ());
114 film_changed (Film::AUDIO_PROCESSOR);
118 Player::setup_pieces ()
120 list<shared_ptr<Piece> > old_pieces = _pieces;
123 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
125 if (!i->paths_valid ()) {
129 shared_ptr<Decoder> decoder;
130 optional<FrameRateChange> frc;
133 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i);
135 decoder.reset (new FFmpegDecoder (fc, _film->log(), _fast));
136 frc = FrameRateChange (fc->active_video_frame_rate(), _film->video_frame_rate());
139 shared_ptr<const DCPContent> dc = dynamic_pointer_cast<const DCPContent> (i);
141 decoder.reset (new DCPDecoder (dc, _film->log(), _fast));
142 frc = FrameRateChange (dc->active_video_frame_rate(), _film->video_frame_rate());
146 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
148 /* See if we can re-use an old ImageDecoder */
149 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
150 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
151 if (imd && imd->content() == ic) {
157 decoder.reset (new ImageDecoder (ic, _film->log()));
160 frc = FrameRateChange (ic->active_video_frame_rate(), _film->video_frame_rate());
164 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i);
166 decoder.reset (new SndfileDecoder (sc, _fast, _film->log()));
168 /* Work out a FrameRateChange for the best overlap video for this content */
169 DCPTime best_overlap_t;
170 shared_ptr<Content> best_overlap;
171 BOOST_FOREACH (shared_ptr<Content> j, _playlist->content ()) {
176 DCPTime const overlap = min (j->end(), i->end()) - max (j->position(), i->position());
177 if (overlap > best_overlap_t) {
179 best_overlap_t = overlap;
184 frc = FrameRateChange (best_overlap->active_video_frame_rate(), _film->video_frame_rate ());
186 /* No video overlap; e.g. if the DCP is just audio */
187 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
191 /* It's questionable whether subtitle content should have a video frame rate; perhaps
192 it should be assumed that any subtitle content has been prepared at the same rate
193 as simultaneous video content (like we do with audio).
196 /* TextSubtitleContent */
197 shared_ptr<const TextSubtitleContent> rc = dynamic_pointer_cast<const TextSubtitleContent> (i);
199 decoder.reset (new TextSubtitleDecoder (rc));
200 frc = FrameRateChange (rc->active_video_frame_rate(), _film->video_frame_rate());
203 /* DCPSubtitleContent */
204 shared_ptr<const DCPSubtitleContent> dsc = dynamic_pointer_cast<const DCPSubtitleContent> (i);
206 decoder.reset (new DCPSubtitleDecoder (dsc));
207 frc = FrameRateChange (dsc->active_video_frame_rate(), _film->video_frame_rate());
210 if (decoder->video && _ignore_video) {
211 decoder->video->set_ignore ();
214 if (decoder->audio && _ignore_audio) {
215 decoder->audio->set_ignore ();
218 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc.get ())));
221 _have_valid_pieces = true;
225 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
227 shared_ptr<Content> c = w.lock ();
233 property == ContentProperty::POSITION ||
234 property == ContentProperty::LENGTH ||
235 property == ContentProperty::TRIM_START ||
236 property == ContentProperty::TRIM_END ||
237 property == ContentProperty::PATH ||
238 property == VideoContentProperty::FRAME_TYPE ||
239 property == DCPContentProperty::CAN_BE_PLAYED ||
240 property == SubtitleContentProperty::COLOUR ||
241 property == SubtitleContentProperty::OUTLINE ||
242 property == SubtitleContentProperty::OUTLINE_COLOUR ||
243 property == FFmpegContentProperty::SUBTITLE_STREAM
246 _have_valid_pieces = false;
250 property == ContentProperty::VIDEO_FRAME_RATE ||
251 property == SubtitleContentProperty::USE ||
252 property == SubtitleContentProperty::X_OFFSET ||
253 property == SubtitleContentProperty::Y_OFFSET ||
254 property == SubtitleContentProperty::X_SCALE ||
255 property == SubtitleContentProperty::Y_SCALE ||
256 property == SubtitleContentProperty::FONTS ||
257 property == VideoContentProperty::CROP ||
258 property == VideoContentProperty::SCALE ||
259 property == VideoContentProperty::FADE_IN ||
260 property == VideoContentProperty::FADE_OUT ||
261 property == VideoContentProperty::COLOUR_CONVERSION
269 Player::set_video_container_size (dcp::Size s)
271 _video_container_size = s;
273 _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
274 _black_image->make_black ();
278 Player::playlist_changed ()
280 _have_valid_pieces = false;
285 Player::film_changed (Film::Property p)
287 /* Here we should notice Film properties that affect our output, and
288 alert listeners that our output now would be different to how it was
289 last time we were run.
292 if (p == Film::CONTAINER) {
294 } else if (p == Film::VIDEO_FRAME_RATE) {
295 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
296 so we need new pieces here.
298 _have_valid_pieces = false;
300 } else if (p == Film::AUDIO_PROCESSOR) {
301 if (_film->audio_processor ()) {
302 _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
308 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
310 list<PositionImage> all;
312 for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
317 /* We will scale the subtitle up to fit _video_container_size */
318 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
320 /* Then we need a corrective translation, consisting of two parts:
322 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
323 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
325 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
326 * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
327 * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
329 * Combining these two translations gives these expressions.
336 dcp::YUV_TO_RGB_REC601,
337 i->image->pixel_format (),
342 lrint (_video_container_size.width * i->rectangle.x),
343 lrint (_video_container_size.height * i->rectangle.y)
352 shared_ptr<PlayerVideo>
353 Player::black_player_video_frame (DCPTime time) const
355 return shared_ptr<PlayerVideo> (
357 shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
361 _video_container_size,
362 _video_container_size,
365 PresetColourConversion::all().front().conversion
370 /** @return All PlayerVideos at the given time. There may be none if the content
371 * at `time' is a DCP which we are passing through (i.e. referring to by reference)
372 * or 2 if we have 3D.
374 list<shared_ptr<PlayerVideo> >
375 Player::get_video (DCPTime time, bool accurate)
377 if (!_have_valid_pieces) {
381 /* Find subtitles for possible burn-in */
383 PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
385 list<PositionImage> sub_images;
387 /* Image subtitles */
388 list<PositionImage> c = transform_image_subtitles (ps.image);
389 copy (c.begin(), c.end(), back_inserter (sub_images));
391 /* Text subtitles (rendered to an image) */
392 if (!ps.text.empty ()) {
393 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
394 copy (s.begin (), s.end (), back_inserter (sub_images));
397 optional<PositionImage> subtitles;
398 if (!sub_images.empty ()) {
399 subtitles = merge (sub_images);
402 /* Find pieces containing video which is happening now */
404 list<shared_ptr<Piece> > ov = overlaps (
406 time + DCPTime::from_frames (1, _film->video_frame_rate ()),
410 list<shared_ptr<PlayerVideo> > pvf;
413 /* No video content at this time */
414 pvf.push_back (black_player_video_frame (time));
416 /* Some video content at this time */
417 shared_ptr<Piece> last = *(ov.rbegin ());
418 VideoFrameType const last_type = last->content->video->frame_type ();
420 /* Get video from appropriate piece(s) */
421 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
423 shared_ptr<VideoDecoder> decoder = piece->decoder->video;
424 DCPOMATIC_ASSERT (decoder);
426 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
427 if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
432 /* always use the last video */
434 /* with a corresponding L/R eye if appropriate */
435 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
436 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
439 /* We want to use this piece */
440 list<ContentVideo> content_video = decoder->get (dcp_to_content_video (piece, time), accurate);
441 if (content_video.empty ()) {
442 pvf.push_back (black_player_video_frame (time));
444 dcp::Size image_size = piece->content->video->scale().size (
445 piece->content->video, _video_container_size, _film->frame_size ()
448 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
450 shared_ptr<PlayerVideo> (
453 content_video_to_dcp (piece, i->frame),
454 piece->content->video->crop (),
455 piece->content->video->fade (i->frame),
457 _video_container_size,
460 piece->content->video->colour_conversion ()
467 /* Discard unused video */
468 decoder->get (dcp_to_content_video (piece, time), accurate);
474 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
475 p->set_subtitle (subtitles.get ());
482 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
483 shared_ptr<AudioBuffers>
484 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
486 if (!_have_valid_pieces) {
490 Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
492 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
493 audio->make_silent ();
495 list<shared_ptr<Piece> > ov = overlaps (time, time + length, has_audio);
500 bool all_referenced = true;
501 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
502 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
503 if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) {
504 /* There is audio content which is not from a DCP or not set to be referenced */
505 all_referenced = false;
509 if (all_referenced && !_play_referenced) {
510 return shared_ptr<AudioBuffers> ();
513 BOOST_FOREACH (shared_ptr<Piece> i, ov) {
515 DCPOMATIC_ASSERT (i->content->audio);
516 shared_ptr<AudioDecoder> decoder = i->decoder->audio;
517 DCPOMATIC_ASSERT (decoder);
519 /* The time that we should request from the content */
520 DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0);
521 Frame request_frames = length_frames;
523 if (request < DCPTime ()) {
524 /* We went off the start of the content, so we will need to offset
525 the stuff we get back.
528 request_frames += request.frames_round (_film->audio_frame_rate ());
529 if (request_frames < 0) {
532 request = DCPTime ();
535 Frame const content_frame = dcp_to_resampled_audio (i, request);
537 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) {
539 if (j->channels() == 0) {
540 /* Some content (e.g. DCPs) can have streams with no channels */
544 /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
545 ContentAudio all = decoder->get (j, content_frame, request_frames, accurate);
548 if (i->content->audio->gain() != 0) {
549 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
550 gain->apply_gain (i->content->audio->gain ());
555 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
556 dcp_mapped->make_silent ();
557 AudioMapping map = j->mapping ();
558 for (int i = 0; i < map.input_channels(); ++i) {
559 for (int j = 0; j < _film->audio_channels(); ++j) {
560 if (map.get (i, j) > 0) {
561 dcp_mapped->accumulate_channel (
571 if (_audio_processor) {
572 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
575 all.audio = dcp_mapped;
577 audio->accumulate_frames (
579 content_frame - all.frame,
580 offset.frames_round (_film->audio_frame_rate()),
581 min (Frame (all.audio->frames()), request_frames)
590 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
592 DCPTime s = t - piece->content->position ();
593 s = min (piece->content->length_after_trim(), s);
594 s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
596 /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
597 then convert that ContentTime to frames at the content's rate. However this fails for
598 situations like content at 29.9978733fps, DCP at 30fps. The accuracy of the Time type is not
599 enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
601 Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
603 return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
607 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
609 /* See comment in dcp_to_content_video */
610 DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
611 return max (DCPTime (), d + piece->content->position ());
615 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
617 DCPTime s = t - piece->content->position ();
618 s = min (piece->content->length_after_trim(), s);
619 /* See notes in dcp_to_content_video */
620 return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
624 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
626 DCPTime s = t - piece->content->position ();
627 s = min (piece->content->length_after_trim(), s);
628 return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
632 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
634 return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
637 /** @param burnt true to return only subtitles to be burnt, false to return only
638 * subtitles that should not be burnt. This parameter will be ignored if
639 * _always_burn_subtitles is true; in this case, all subtitles will be returned.
642 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
644 list<shared_ptr<Piece> > subs = overlaps (time, time + length, has_subtitle);
646 PlayerSubtitles ps (time, length);
648 for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
649 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
653 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->content);
654 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
658 shared_ptr<SubtitleDecoder> subtitle_decoder = (*j)->decoder->subtitle;
659 ContentTime const from = dcp_to_content_subtitle (*j, time);
660 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
661 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
663 list<ContentImageSubtitle> image = subtitle_decoder->get_image (ContentTimePeriod (from, to), starting, accurate);
664 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
666 /* Apply content's subtitle offsets */
667 i->sub.rectangle.x += (*j)->content->subtitle->x_offset ();
668 i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
670 /* Apply content's subtitle scale */
671 i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
672 i->sub.rectangle.height *= (*j)->content->subtitle->y_scale ();
674 /* Apply a corrective translation to keep the subtitle centred after that scale */
675 i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1);
676 i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1);
678 ps.image.push_back (i->sub);
681 list<ContentTextSubtitle> text = subtitle_decoder->get_text (ContentTimePeriod (from, to), starting, accurate);
682 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
683 BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
684 s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ());
685 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
686 float const xs = (*j)->content->subtitle->x_scale();
687 float const ys = (*j)->content->subtitle->y_scale();
688 float size = s.size();
690 /* Adjust size to express the common part of the scaling;
691 e.g. if xs = ys = 0.5 we scale size by 2.
693 if (xs > 1e-5 && ys > 1e-5) {
694 size *= 1 / min (1 / xs, 1 / ys);
698 /* Then express aspect ratio changes */
699 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
700 s.set_aspect_adjust (xs / ys);
702 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
703 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
704 ps.text.push_back (s);
705 ps.add_fonts ((*j)->content->subtitle->fonts ());
713 list<shared_ptr<Font> >
714 Player::get_subtitle_fonts ()
716 if (!_have_valid_pieces) {
720 list<shared_ptr<Font> > fonts;
721 BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
722 if (p->content->subtitle) {
723 /* XXX: things may go wrong if there are duplicate font IDs
724 with different font files.
726 list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
727 copy (f.begin(), f.end(), back_inserter (fonts));
734 /** Set this player never to produce any video data */
736 Player::set_ignore_video ()
738 _ignore_video = true;
741 /** Set this player never to produce any audio data */
743 Player::set_ignore_audio ()
745 _ignore_audio = true;
748 /** Set whether or not this player should always burn text subtitles into the image,
749 * regardless of the content settings.
750 * @param burn true to always burn subtitles, false to obey content settings.
753 Player::set_always_burn_subtitles (bool burn)
755 _always_burn_subtitles = burn;
762 _have_valid_pieces = false;
766 Player::set_play_referenced ()
768 _play_referenced = true;
769 _have_valid_pieces = false;
772 list<ReferencedReelAsset>
773 Player::get_reel_assets ()
775 list<ReferencedReelAsset> a;
777 BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
778 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
783 scoped_ptr<DCPDecoder> decoder;
785 decoder.reset (new DCPDecoder (j, _film->log(), false));
791 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
792 DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
793 if (j->reference_video ()) {
795 ReferencedReelAsset (
797 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
802 if (j->reference_audio ()) {
804 ReferencedReelAsset (
806 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
811 if (j->reference_subtitle ()) {
812 DCPOMATIC_ASSERT (k->main_subtitle ());
814 ReferencedReelAsset (
816 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
821 /* Assume that main picture duration is the length of the reel */
822 offset += k->main_picture()->duration ();
829 list<shared_ptr<Piece> >
830 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
832 if (!_have_valid_pieces) {
836 list<shared_ptr<Piece> > overlaps;
837 BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
838 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
839 overlaps.push_back (i);