Set AudioDecoder::fast a different way.
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "player.h"
22 #include "film.h"
23 #include "audio_buffers.h"
24 #include "content_audio.h"
25 #include "dcp_content.h"
26 #include "job.h"
27 #include "image.h"
28 #include "raw_image_proxy.h"
29 #include "ratio.h"
30 #include "log.h"
31 #include "render_subtitles.h"
32 #include "config.h"
33 #include "content_video.h"
34 #include "player_video.h"
35 #include "frame_rate_change.h"
36 #include "audio_processor.h"
37 #include "playlist.h"
38 #include "referenced_reel_asset.h"
39 #include "decoder_factory.h"
40 #include "decoder.h"
41 #include "video_decoder.h"
42 #include "audio_decoder.h"
43 #include "subtitle_content.h"
44 #include "subtitle_decoder.h"
45 #include "ffmpeg_content.h"
46 #include "audio_content.h"
47 #include "content_subtitle.h"
48 #include "dcp_decoder.h"
49 #include "image_decoder.h"
50 #include <dcp/reel.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>
55 #include <stdint.h>
56 #include <algorithm>
57 #include <iostream>
58
59 #include "i18n.h"
60
61 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
62
63 using std::list;
64 using std::cout;
65 using std::min;
66 using std::max;
67 using std::min;
68 using std::vector;
69 using std::pair;
70 using std::map;
71 using std::make_pair;
72 using std::copy;
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;
78
79 static bool
80 has_video (Content* c)
81 {
82         return static_cast<bool>(c->video);
83 }
84
85 static bool
86 has_audio (Content* c)
87 {
88         return static_cast<bool>(c->audio);
89 }
90
91 static bool
92 has_subtitle (Content* c)
93 {
94         return static_cast<bool>(c->subtitle);
95 }
96
97 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
98         : _film (film)
99         , _playlist (playlist)
100         , _have_valid_pieces (false)
101         , _ignore_video (false)
102         , _ignore_audio (false)
103         , _always_burn_subtitles (false)
104         , _fast (false)
105         , _play_referenced (false)
106 {
107         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
108         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
109         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
110         set_video_container_size (_film->frame_size ());
111
112         film_changed (Film::AUDIO_PROCESSOR);
113 }
114
115 void
116 Player::setup_pieces ()
117 {
118         _pieces.clear ();
119
120         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
121
122                 if (!i->paths_valid ()) {
123                         continue;
124                 }
125
126                 shared_ptr<Decoder> decoder = decoder_factory (i, _film->log());
127                 FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
128
129                 if (!decoder) {
130                         /* Not something that we can decode; e.g. Atmos content */
131                         continue;
132                 }
133
134                 if (decoder->video && _ignore_video) {
135                         decoder->video->set_ignore ();
136                 }
137
138                 if (decoder->audio && _ignore_audio) {
139                         decoder->audio->set_ignore ();
140                 }
141
142                 if (decoder->audio && _fast) {
143                         decoder->audio->set_fast ();
144                 }
145
146                 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc)));
147         }
148
149         _have_valid_pieces = true;
150 }
151
152 void
153 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
154 {
155         shared_ptr<Content> c = w.lock ();
156         if (!c) {
157                 return;
158         }
159
160         if (
161                 property == ContentProperty::POSITION ||
162                 property == ContentProperty::LENGTH ||
163                 property == ContentProperty::TRIM_START ||
164                 property == ContentProperty::TRIM_END ||
165                 property == ContentProperty::PATH ||
166                 property == VideoContentProperty::FRAME_TYPE ||
167                 property == DCPContentProperty::CAN_BE_PLAYED ||
168                 property == SubtitleContentProperty::COLOUR ||
169                 property == SubtitleContentProperty::OUTLINE ||
170                 property == SubtitleContentProperty::OUTLINE_COLOUR ||
171                 property == FFmpegContentProperty::SUBTITLE_STREAM
172                 ) {
173
174                 _have_valid_pieces = false;
175                 Changed (frequent);
176
177         } else if (
178                 property == ContentProperty::VIDEO_FRAME_RATE ||
179                 property == SubtitleContentProperty::USE ||
180                 property == SubtitleContentProperty::X_OFFSET ||
181                 property == SubtitleContentProperty::Y_OFFSET ||
182                 property == SubtitleContentProperty::X_SCALE ||
183                 property == SubtitleContentProperty::Y_SCALE ||
184                 property == SubtitleContentProperty::FONTS ||
185                 property == VideoContentProperty::CROP ||
186                 property == VideoContentProperty::SCALE ||
187                 property == VideoContentProperty::FADE_IN ||
188                 property == VideoContentProperty::FADE_OUT ||
189                 property == VideoContentProperty::COLOUR_CONVERSION
190                 ) {
191
192                 Changed (frequent);
193         }
194 }
195
196 void
197 Player::set_video_container_size (dcp::Size s)
198 {
199         _video_container_size = s;
200
201         _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
202         _black_image->make_black ();
203 }
204
205 void
206 Player::playlist_changed ()
207 {
208         _have_valid_pieces = false;
209         Changed (false);
210 }
211
212 void
213 Player::film_changed (Film::Property p)
214 {
215         /* Here we should notice Film properties that affect our output, and
216            alert listeners that our output now would be different to how it was
217            last time we were run.
218         */
219
220         if (p == Film::CONTAINER) {
221                 Changed (false);
222         } else if (p == Film::VIDEO_FRAME_RATE) {
223                 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
224                    so we need new pieces here.
225                 */
226                 _have_valid_pieces = false;
227                 Changed (false);
228         } else if (p == Film::AUDIO_PROCESSOR) {
229                 if (_film->audio_processor ()) {
230                         _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
231                 }
232         }
233 }
234
235 list<PositionImage>
236 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
237 {
238         list<PositionImage> all;
239
240         for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
241                 if (!i->image) {
242                         continue;
243                 }
244
245                 /* We will scale the subtitle up to fit _video_container_size */
246                 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
247
248                 /* Then we need a corrective translation, consisting of two parts:
249                  *
250                  * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
251                  *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
252                  *
253                  * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
254                  *     (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
255                  *     (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
256                  *
257                  * Combining these two translations gives these expressions.
258                  */
259
260                 all.push_back (
261                         PositionImage (
262                                 i->image->scale (
263                                         scaled_size,
264                                         dcp::YUV_TO_RGB_REC601,
265                                         i->image->pixel_format (),
266                                         true,
267                                         _fast
268                                         ),
269                                 Position<int> (
270                                         lrint (_video_container_size.width * i->rectangle.x),
271                                         lrint (_video_container_size.height * i->rectangle.y)
272                                         )
273                                 )
274                         );
275         }
276
277         return all;
278 }
279
280 shared_ptr<PlayerVideo>
281 Player::black_player_video_frame (DCPTime time) const
282 {
283         return shared_ptr<PlayerVideo> (
284                 new PlayerVideo (
285                         shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
286                         time,
287                         Crop (),
288                         optional<double> (),
289                         _video_container_size,
290                         _video_container_size,
291                         EYES_BOTH,
292                         PART_WHOLE,
293                         PresetColourConversion::all().front().conversion
294                 )
295         );
296 }
297
298 /** @return All PlayerVideos at the given time.  There may be none if the content
299  *  at `time' is a DCP which we are passing through (i.e. referring to by reference)
300  *  or 2 if we have 3D.
301  */
302 list<shared_ptr<PlayerVideo> >
303 Player::get_video (DCPTime time, bool accurate)
304 {
305         if (!_have_valid_pieces) {
306                 setup_pieces ();
307         }
308
309         /* Find subtitles for possible burn-in */
310
311         PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
312
313         list<PositionImage> sub_images;
314
315         /* Image subtitles */
316         list<PositionImage> c = transform_image_subtitles (ps.image);
317         copy (c.begin(), c.end(), back_inserter (sub_images));
318
319         /* Text subtitles (rendered to an image) */
320         if (!ps.text.empty ()) {
321                 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
322                 copy (s.begin (), s.end (), back_inserter (sub_images));
323         }
324
325         optional<PositionImage> subtitles;
326         if (!sub_images.empty ()) {
327                 subtitles = merge (sub_images);
328         }
329
330         /* Find pieces containing video which is happening now */
331
332         list<shared_ptr<Piece> > ov = overlaps (
333                 time,
334                 time + DCPTime::from_frames (1, _film->video_frame_rate ()),
335                 &has_video
336                 );
337
338         list<shared_ptr<PlayerVideo> > pvf;
339
340         if (ov.empty ()) {
341                 /* No video content at this time */
342                 pvf.push_back (black_player_video_frame (time));
343         } else {
344                 /* Some video content at this time */
345                 shared_ptr<Piece> last = *(ov.rbegin ());
346                 VideoFrameType const last_type = last->content->video->frame_type ();
347
348                 /* Get video from appropriate piece(s) */
349                 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
350
351                         shared_ptr<VideoDecoder> decoder = piece->decoder->video;
352                         DCPOMATIC_ASSERT (decoder);
353
354                         shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
355                         if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
356                                 continue;
357                         }
358
359                         bool const use =
360                                 /* always use the last video */
361                                 piece == last ||
362                                 /* with a corresponding L/R eye if appropriate */
363                                 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
364                                 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
365
366                         if (use) {
367                                 /* We want to use this piece */
368                                 list<ContentVideo> content_video = decoder->get (dcp_to_content_video (piece, time), accurate);
369                                 if (content_video.empty ()) {
370                                         pvf.push_back (black_player_video_frame (time));
371                                 } else {
372                                         dcp::Size image_size = piece->content->video->scale().size (
373                                                 piece->content->video, _video_container_size, _film->frame_size ()
374                                                 );
375
376                                         for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
377                                                 pvf.push_back (
378                                                         shared_ptr<PlayerVideo> (
379                                                                 new PlayerVideo (
380                                                                         i->image,
381                                                                         time,
382                                                                         piece->content->video->crop (),
383                                                                         piece->content->video->fade (i->frame.index()),
384                                                                         image_size,
385                                                                         _video_container_size,
386                                                                         i->frame.eyes(),
387                                                                         i->part,
388                                                                         piece->content->video->colour_conversion ()
389                                                                         )
390                                                                 )
391                                                         );
392                                         }
393                                 }
394                         } else {
395                                 /* Discard unused video */
396                                 decoder->get (dcp_to_content_video (piece, time), accurate);
397                         }
398                 }
399         }
400
401         if (subtitles) {
402                 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
403                         p->set_subtitle (subtitles.get ());
404                 }
405         }
406
407         return pvf;
408 }
409
410 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
411 shared_ptr<AudioBuffers>
412 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
413 {
414         if (!_have_valid_pieces) {
415                 setup_pieces ();
416         }
417
418         Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
419
420         shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
421         audio->make_silent ();
422
423         list<shared_ptr<Piece> > ov = overlaps (time, time + length, has_audio);
424         if (ov.empty ()) {
425                 return audio;
426         }
427
428         bool all_referenced = true;
429         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
430                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
431                 if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) {
432                         /* There is audio content which is not from a DCP or not set to be referenced */
433                         all_referenced = false;
434                 }
435         }
436
437         if (all_referenced && !_play_referenced) {
438                 return shared_ptr<AudioBuffers> ();
439         }
440
441         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
442
443                 DCPOMATIC_ASSERT (i->content->audio);
444                 shared_ptr<AudioDecoder> decoder = i->decoder->audio;
445                 DCPOMATIC_ASSERT (decoder);
446
447                 /* The time that we should request from the content */
448                 DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0);
449                 Frame request_frames = length_frames;
450                 DCPTime offset;
451                 if (request < DCPTime ()) {
452                         /* We went off the start of the content, so we will need to offset
453                            the stuff we get back.
454                         */
455                         offset = -request;
456                         request_frames += request.frames_round (_film->audio_frame_rate ());
457                         if (request_frames < 0) {
458                                 request_frames = 0;
459                         }
460                         request = DCPTime ();
461                 }
462
463                 Frame const content_frame = dcp_to_resampled_audio (i, request);
464
465                 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) {
466
467                         if (j->channels() == 0) {
468                                 /* Some content (e.g. DCPs) can have streams with no channels */
469                                 continue;
470                         }
471
472                         /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
473                         ContentAudio all = decoder->get (j, content_frame, request_frames, accurate);
474
475                         /* Gain */
476                         if (i->content->audio->gain() != 0) {
477                                 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
478                                 gain->apply_gain (i->content->audio->gain ());
479                                 all.audio = gain;
480                         }
481
482                         /* Remap channels */
483                         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
484                         dcp_mapped->make_silent ();
485                         AudioMapping map = j->mapping ();
486                         for (int i = 0; i < map.input_channels(); ++i) {
487                                 for (int j = 0; j < _film->audio_channels(); ++j) {
488                                         if (map.get (i, j) > 0) {
489                                                 dcp_mapped->accumulate_channel (
490                                                         all.audio.get(),
491                                                         i,
492                                                         j,
493                                                         map.get (i, j)
494                                                         );
495                                         }
496                                 }
497                         }
498
499                         if (_audio_processor) {
500                                 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
501                         }
502
503                         all.audio = dcp_mapped;
504
505                         audio->accumulate_frames (
506                                 all.audio.get(),
507                                 content_frame - all.frame,
508                                 offset.frames_round (_film->audio_frame_rate()),
509                                 min (Frame (all.audio->frames()), request_frames)
510                                 );
511                 }
512         }
513
514         return audio;
515 }
516
517 Frame
518 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
519 {
520         DCPTime s = t - piece->content->position ();
521         s = min (piece->content->length_after_trim(), s);
522         s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
523
524         /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
525            then convert that ContentTime to frames at the content's rate.  However this fails for
526            situations like content at 29.9978733fps, DCP at 30fps.  The accuracy of the Time type is not
527            enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
528
529            Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
530         */
531         return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
532 }
533
534 DCPTime
535 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
536 {
537         /* See comment in dcp_to_content_video */
538         DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
539         return max (DCPTime (), d + piece->content->position ());
540 }
541
542 Frame
543 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
544 {
545         DCPTime s = t - piece->content->position ();
546         s = min (piece->content->length_after_trim(), s);
547         /* See notes in dcp_to_content_video */
548         return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
549 }
550
551 ContentTime
552 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
553 {
554         DCPTime s = t - piece->content->position ();
555         s = min (piece->content->length_after_trim(), s);
556         return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
557 }
558
559 DCPTime
560 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
561 {
562         return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
563 }
564
565 /** @param burnt true to return only subtitles to be burnt, false to return only
566  *  subtitles that should not be burnt.  This parameter will be ignored if
567  *  _always_burn_subtitles is true; in this case, all subtitles will be returned.
568  */
569 PlayerSubtitles
570 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
571 {
572         list<shared_ptr<Piece> > subs = overlaps (time, time + length, has_subtitle);
573
574         PlayerSubtitles ps (time, length);
575
576         for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
577                 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
578                         continue;
579                 }
580
581                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->content);
582                 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
583                         continue;
584                 }
585
586                 shared_ptr<SubtitleDecoder> subtitle_decoder = (*j)->decoder->subtitle;
587                 ContentTime const from = dcp_to_content_subtitle (*j, time);
588                 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
589                 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
590
591                 list<ContentImageSubtitle> image = subtitle_decoder->get_image (ContentTimePeriod (from, to), starting, accurate);
592                 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
593
594                         /* Apply content's subtitle offsets */
595                         i->sub.rectangle.x += (*j)->content->subtitle->x_offset ();
596                         i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
597
598                         /* Apply content's subtitle scale */
599                         i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
600                         i->sub.rectangle.height *= (*j)->content->subtitle->y_scale ();
601
602                         /* Apply a corrective translation to keep the subtitle centred after that scale */
603                         i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1);
604                         i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1);
605
606                         ps.image.push_back (i->sub);
607                 }
608
609                 list<ContentTextSubtitle> text = subtitle_decoder->get_text (ContentTimePeriod (from, to), starting, accurate);
610                 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
611                         BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
612                                 s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ());
613                                 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
614                                 float const xs = (*j)->content->subtitle->x_scale();
615                                 float const ys = (*j)->content->subtitle->y_scale();
616                                 float size = s.size();
617
618                                 /* Adjust size to express the common part of the scaling;
619                                    e.g. if xs = ys = 0.5 we scale size by 2.
620                                 */
621                                 if (xs > 1e-5 && ys > 1e-5) {
622                                         size *= 1 / min (1 / xs, 1 / ys);
623                                 }
624                                 s.set_size (size);
625
626                                 /* Then express aspect ratio changes */
627                                 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
628                                         s.set_aspect_adjust (xs / ys);
629                                 }
630                                 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
631                                 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
632                                 ps.text.push_back (s);
633                                 ps.add_fonts ((*j)->content->subtitle->fonts ());
634                         }
635                 }
636         }
637
638         return ps;
639 }
640
641 list<shared_ptr<Font> >
642 Player::get_subtitle_fonts ()
643 {
644         if (!_have_valid_pieces) {
645                 setup_pieces ();
646         }
647
648         list<shared_ptr<Font> > fonts;
649         BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
650                 if (p->content->subtitle) {
651                         /* XXX: things may go wrong if there are duplicate font IDs
652                            with different font files.
653                         */
654                         list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
655                         copy (f.begin(), f.end(), back_inserter (fonts));
656                 }
657         }
658
659         return fonts;
660 }
661
662 /** Set this player never to produce any video data */
663 void
664 Player::set_ignore_video ()
665 {
666         _ignore_video = true;
667 }
668
669 /** Set this player never to produce any audio data */
670 void
671 Player::set_ignore_audio ()
672 {
673         _ignore_audio = true;
674 }
675
676 /** Set whether or not this player should always burn text subtitles into the image,
677  *  regardless of the content settings.
678  *  @param burn true to always burn subtitles, false to obey content settings.
679  */
680 void
681 Player::set_always_burn_subtitles (bool burn)
682 {
683         _always_burn_subtitles = burn;
684 }
685
686 void
687 Player::set_fast ()
688 {
689         _fast = true;
690         _have_valid_pieces = false;
691 }
692
693 void
694 Player::set_play_referenced ()
695 {
696         _play_referenced = true;
697         _have_valid_pieces = false;
698 }
699
700 list<ReferencedReelAsset>
701 Player::get_reel_assets ()
702 {
703         list<ReferencedReelAsset> a;
704
705         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
706                 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
707                 if (!j) {
708                         continue;
709                 }
710
711                 scoped_ptr<DCPDecoder> decoder;
712                 try {
713                         decoder.reset (new DCPDecoder (j, _film->log()));
714                 } catch (...) {
715                         return a;
716                 }
717
718                 int64_t offset = 0;
719                 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
720                         DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
721                         if (j->reference_video ()) {
722                                 a.push_back (
723                                         ReferencedReelAsset (
724                                                 k->main_picture (),
725                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
726                                                 )
727                                         );
728                         }
729
730                         if (j->reference_audio ()) {
731                                 a.push_back (
732                                         ReferencedReelAsset (
733                                                 k->main_sound (),
734                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
735                                                 )
736                                         );
737                         }
738
739                         if (j->reference_subtitle ()) {
740                                 DCPOMATIC_ASSERT (k->main_subtitle ());
741                                 a.push_back (
742                                         ReferencedReelAsset (
743                                                 k->main_subtitle (),
744                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
745                                                 )
746                                         );
747                         }
748
749                         /* Assume that main picture duration is the length of the reel */
750                         offset += k->main_picture()->duration ();
751                 }
752         }
753
754         return a;
755 }
756
757 list<shared_ptr<Piece> >
758 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
759 {
760         if (!_have_valid_pieces) {
761                 setup_pieces ();
762         }
763
764         list<shared_ptr<Piece> > overlaps;
765         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
766                 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
767                         overlaps.push_back (i);
768                 }
769         }
770
771         return overlaps;
772 }