Make burnt-in subtitle outline width configurable (#940).
[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                 shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
147                 if (dcp && _play_referenced) {
148                         dcp->set_decode_referenced ();
149                 }
150
151                 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc)));
152         }
153
154         _have_valid_pieces = true;
155 }
156
157 void
158 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
159 {
160         shared_ptr<Content> c = w.lock ();
161         if (!c) {
162                 return;
163         }
164
165         if (
166                 property == ContentProperty::POSITION ||
167                 property == ContentProperty::LENGTH ||
168                 property == ContentProperty::TRIM_START ||
169                 property == ContentProperty::TRIM_END ||
170                 property == ContentProperty::PATH ||
171                 property == VideoContentProperty::FRAME_TYPE ||
172                 property == DCPContentProperty::CAN_BE_PLAYED ||
173                 property == SubtitleContentProperty::COLOUR ||
174                 property == SubtitleContentProperty::OUTLINE ||
175                 property == SubtitleContentProperty::SHADOW ||
176                 property == SubtitleContentProperty::EFFECT_COLOUR ||
177                 property == FFmpegContentProperty::SUBTITLE_STREAM ||
178                 property == VideoContentProperty::COLOUR_CONVERSION
179                 ) {
180
181                 _have_valid_pieces = false;
182                 Changed (frequent);
183
184         } else if (
185                 property == SubtitleContentProperty::LINE_SPACING ||
186                 property == SubtitleContentProperty::OUTLINE_WIDTH ||
187                 property == SubtitleContentProperty::Y_SCALE
188                 ) {
189
190                 /* These changes just need the pieces' decoders to be reset.
191                    It's quite possible that other changes could be handled by
192                    this branch rather than the _have_valid_pieces = false branch
193                    above.  This would make things a lot faster.
194                 */
195
196                 reset_pieces ();
197                 Changed (frequent);
198
199         } else if (
200                 property == ContentProperty::VIDEO_FRAME_RATE ||
201                 property == SubtitleContentProperty::USE ||
202                 property == SubtitleContentProperty::X_OFFSET ||
203                 property == SubtitleContentProperty::Y_OFFSET ||
204                 property == SubtitleContentProperty::X_SCALE ||
205                 property == SubtitleContentProperty::FONTS ||
206                 property == VideoContentProperty::CROP ||
207                 property == VideoContentProperty::SCALE ||
208                 property == VideoContentProperty::FADE_IN ||
209                 property == VideoContentProperty::FADE_OUT
210                 ) {
211
212                 Changed (frequent);
213         }
214 }
215
216 void
217 Player::set_video_container_size (dcp::Size s)
218 {
219         _video_container_size = s;
220
221         _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
222         _black_image->make_black ();
223 }
224
225 void
226 Player::playlist_changed ()
227 {
228         _have_valid_pieces = false;
229         Changed (false);
230 }
231
232 void
233 Player::film_changed (Film::Property p)
234 {
235         /* Here we should notice Film properties that affect our output, and
236            alert listeners that our output now would be different to how it was
237            last time we were run.
238         */
239
240         if (p == Film::CONTAINER) {
241                 Changed (false);
242         } else if (p == Film::VIDEO_FRAME_RATE) {
243                 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
244                    so we need new pieces here.
245                 */
246                 _have_valid_pieces = false;
247                 Changed (false);
248         } else if (p == Film::AUDIO_PROCESSOR) {
249                 if (_film->audio_processor ()) {
250                         _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
251                 }
252         }
253 }
254
255 list<PositionImage>
256 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
257 {
258         list<PositionImage> all;
259
260         for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
261                 if (!i->image) {
262                         continue;
263                 }
264
265                 /* We will scale the subtitle up to fit _video_container_size */
266                 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
267
268                 /* Then we need a corrective translation, consisting of two parts:
269                  *
270                  * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
271                  *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
272                  *
273                  * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
274                  *     (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
275                  *     (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
276                  *
277                  * Combining these two translations gives these expressions.
278                  */
279
280                 all.push_back (
281                         PositionImage (
282                                 i->image->scale (
283                                         scaled_size,
284                                         dcp::YUV_TO_RGB_REC601,
285                                         i->image->pixel_format (),
286                                         true,
287                                         _fast
288                                         ),
289                                 Position<int> (
290                                         lrint (_video_container_size.width * i->rectangle.x),
291                                         lrint (_video_container_size.height * i->rectangle.y)
292                                         )
293                                 )
294                         );
295         }
296
297         return all;
298 }
299
300 shared_ptr<PlayerVideo>
301 Player::black_player_video_frame (DCPTime time) const
302 {
303         return shared_ptr<PlayerVideo> (
304                 new PlayerVideo (
305                         shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
306                         time,
307                         Crop (),
308                         optional<double> (),
309                         _video_container_size,
310                         _video_container_size,
311                         EYES_BOTH,
312                         PART_WHOLE,
313                         PresetColourConversion::all().front().conversion
314                 )
315         );
316 }
317
318 /** @return All PlayerVideos at the given time.  There may be none if the content
319  *  at `time' is a DCP which we are passing through (i.e. referring to by reference)
320  *  or 2 if we have 3D.
321  */
322 list<shared_ptr<PlayerVideo> >
323 Player::get_video (DCPTime time, bool accurate)
324 {
325         if (!_have_valid_pieces) {
326                 setup_pieces ();
327         }
328
329         /* Find subtitles for possible burn-in */
330
331         PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
332
333         list<PositionImage> sub_images;
334
335         /* Image subtitles */
336         list<PositionImage> c = transform_image_subtitles (ps.image);
337         copy (c.begin(), c.end(), back_inserter (sub_images));
338
339         /* Text subtitles (rendered to an image) */
340         if (!ps.text.empty ()) {
341                 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size, time);
342                 copy (s.begin (), s.end (), back_inserter (sub_images));
343         }
344
345         optional<PositionImage> subtitles;
346         if (!sub_images.empty ()) {
347                 subtitles = merge (sub_images);
348         }
349
350         /* Find pieces containing video which is happening now */
351
352         list<shared_ptr<Piece> > ov = overlaps (
353                 time,
354                 time + DCPTime::from_frames (1, _film->video_frame_rate ()),
355                 &has_video
356                 );
357
358         list<shared_ptr<PlayerVideo> > pvf;
359
360         if (ov.empty ()) {
361                 /* No video content at this time */
362                 pvf.push_back (black_player_video_frame (time));
363         } else {
364                 /* Some video content at this time */
365                 shared_ptr<Piece> last = *(ov.rbegin ());
366                 VideoFrameType const last_type = last->content->video->frame_type ();
367
368                 /* Get video from appropriate piece(s) */
369                 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
370
371                         shared_ptr<VideoDecoder> decoder = piece->decoder->video;
372                         DCPOMATIC_ASSERT (decoder);
373
374                         shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
375                         if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
376                                 continue;
377                         }
378
379                         bool const use =
380                                 /* always use the last video */
381                                 piece == last ||
382                                 /* with a corresponding L/R eye if appropriate */
383                                 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
384                                 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
385
386                         if (use) {
387                                 /* We want to use this piece */
388                                 list<ContentVideo> content_video = decoder->get (dcp_to_content_video (piece, time), accurate);
389                                 if (content_video.empty ()) {
390                                         pvf.push_back (black_player_video_frame (time));
391                                 } else {
392                                         dcp::Size image_size = piece->content->video->scale().size (
393                                                 piece->content->video, _video_container_size, _film->frame_size ()
394                                                 );
395
396                                         for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
397                                                 pvf.push_back (
398                                                         shared_ptr<PlayerVideo> (
399                                                                 new PlayerVideo (
400                                                                         i->image,
401                                                                         time,
402                                                                         piece->content->video->crop (),
403                                                                         piece->content->video->fade (i->frame.index()),
404                                                                         image_size,
405                                                                         _video_container_size,
406                                                                         i->frame.eyes(),
407                                                                         i->part,
408                                                                         piece->content->video->colour_conversion ()
409                                                                         )
410                                                                 )
411                                                         );
412                                         }
413                                 }
414                         } else {
415                                 /* Discard unused video */
416                                 decoder->get (dcp_to_content_video (piece, time), accurate);
417                         }
418                 }
419         }
420
421         if (subtitles) {
422                 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
423                         p->set_subtitle (subtitles.get ());
424                 }
425         }
426
427         return pvf;
428 }
429
430 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
431 shared_ptr<AudioBuffers>
432 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
433 {
434         if (!_have_valid_pieces) {
435                 setup_pieces ();
436         }
437
438         Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
439
440         shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
441         audio->make_silent ();
442
443         list<shared_ptr<Piece> > ov = overlaps (time, time + length, has_audio);
444         if (ov.empty ()) {
445                 return audio;
446         }
447
448         bool all_referenced = true;
449         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
450                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
451                 if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) {
452                         /* There is audio content which is not from a DCP or not set to be referenced */
453                         all_referenced = false;
454                 }
455         }
456
457         if (all_referenced && !_play_referenced) {
458                 return shared_ptr<AudioBuffers> ();
459         }
460
461         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
462
463                 DCPOMATIC_ASSERT (i->content->audio);
464                 shared_ptr<AudioDecoder> decoder = i->decoder->audio;
465                 DCPOMATIC_ASSERT (decoder);
466
467                 /* The time that we should request from the content */
468                 DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0);
469                 Frame request_frames = length_frames;
470                 DCPTime offset;
471                 if (request < DCPTime ()) {
472                         /* We went off the start of the content, so we will need to offset
473                            the stuff we get back.
474                         */
475                         offset = -request;
476                         request_frames += request.frames_round (_film->audio_frame_rate ());
477                         if (request_frames < 0) {
478                                 request_frames = 0;
479                         }
480                         request = DCPTime ();
481                 }
482
483                 Frame const content_frame = dcp_to_resampled_audio (i, request);
484
485                 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) {
486
487                         if (j->channels() == 0) {
488                                 /* Some content (e.g. DCPs) can have streams with no channels */
489                                 continue;
490                         }
491
492                         /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
493                         ContentAudio all = decoder->get (j, content_frame, request_frames, accurate);
494
495                         /* Gain */
496                         if (i->content->audio->gain() != 0) {
497                                 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
498                                 gain->apply_gain (i->content->audio->gain ());
499                                 all.audio = gain;
500                         }
501
502                         /* Remap channels */
503                         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
504                         dcp_mapped->make_silent ();
505                         AudioMapping map = j->mapping ();
506                         for (int i = 0; i < map.input_channels(); ++i) {
507                                 for (int j = 0; j < _film->audio_channels(); ++j) {
508                                         if (map.get (i, j) > 0) {
509                                                 dcp_mapped->accumulate_channel (
510                                                         all.audio.get(),
511                                                         i,
512                                                         j,
513                                                         map.get (i, j)
514                                                         );
515                                         }
516                                 }
517                         }
518
519                         if (_audio_processor) {
520                                 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
521                         }
522
523                         all.audio = dcp_mapped;
524
525                         audio->accumulate_frames (
526                                 all.audio.get(),
527                                 content_frame - all.frame,
528                                 offset.frames_round (_film->audio_frame_rate()),
529                                 min (Frame (all.audio->frames()), request_frames)
530                                 );
531                 }
532         }
533
534         return audio;
535 }
536
537 Frame
538 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
539 {
540         DCPTime s = t - piece->content->position ();
541         s = min (piece->content->length_after_trim(), s);
542         s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
543
544         /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
545            then convert that ContentTime to frames at the content's rate.  However this fails for
546            situations like content at 29.9978733fps, DCP at 30fps.  The accuracy of the Time type is not
547            enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
548
549            Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
550         */
551         return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
552 }
553
554 DCPTime
555 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
556 {
557         /* See comment in dcp_to_content_video */
558         DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
559         return max (DCPTime (), d + piece->content->position ());
560 }
561
562 Frame
563 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
564 {
565         DCPTime s = t - piece->content->position ();
566         s = min (piece->content->length_after_trim(), s);
567         /* See notes in dcp_to_content_video */
568         return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
569 }
570
571 ContentTime
572 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
573 {
574         DCPTime s = t - piece->content->position ();
575         s = min (piece->content->length_after_trim(), s);
576         return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
577 }
578
579 DCPTime
580 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
581 {
582         return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
583 }
584
585 /** @param burnt true to return only subtitles to be burnt, false to return only
586  *  subtitles that should not be burnt.  This parameter will be ignored if
587  *  _always_burn_subtitles is true; in this case, all subtitles will be returned.
588  */
589 PlayerSubtitles
590 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
591 {
592         list<shared_ptr<Piece> > subs = overlaps (time, time + length, has_subtitle);
593
594         PlayerSubtitles ps (time);
595
596         for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
597                 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
598                         continue;
599                 }
600
601                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->content);
602                 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
603                         continue;
604                 }
605
606                 shared_ptr<SubtitleDecoder> subtitle_decoder = (*j)->decoder->subtitle;
607                 ContentTime const from = dcp_to_content_subtitle (*j, time);
608                 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
609                 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
610
611                 list<ContentImageSubtitle> image = subtitle_decoder->get_image (ContentTimePeriod (from, to), starting, accurate);
612                 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
613
614                         /* Apply content's subtitle offsets */
615                         i->sub.rectangle.x += (*j)->content->subtitle->x_offset ();
616                         i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
617
618                         /* Apply content's subtitle scale */
619                         i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
620                         i->sub.rectangle.height *= (*j)->content->subtitle->y_scale ();
621
622                         /* Apply a corrective translation to keep the subtitle centred after that scale */
623                         i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1);
624                         i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1);
625
626                         ps.image.push_back (i->sub);
627                 }
628
629                 list<ContentTextSubtitle> text = subtitle_decoder->get_text (ContentTimePeriod (from, to), starting, accurate);
630                 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
631                         BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
632                                 s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ());
633                                 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
634                                 float const xs = (*j)->content->subtitle->x_scale();
635                                 float const ys = (*j)->content->subtitle->y_scale();
636                                 float size = s.size();
637
638                                 /* Adjust size to express the common part of the scaling;
639                                    e.g. if xs = ys = 0.5 we scale size by 2.
640                                 */
641                                 if (xs > 1e-5 && ys > 1e-5) {
642                                         size *= 1 / min (1 / xs, 1 / ys);
643                                 }
644                                 s.set_size (size);
645
646                                 /* Then express aspect ratio changes */
647                                 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
648                                         s.set_aspect_adjust (xs / ys);
649                                 }
650                                 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
651                                 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
652                                 ps.text.push_back (SubtitleString (s, (*j)->content->subtitle->outline_width()));
653                                 ps.add_fonts ((*j)->content->subtitle->fonts ());
654                         }
655                 }
656         }
657
658         return ps;
659 }
660
661 list<shared_ptr<Font> >
662 Player::get_subtitle_fonts ()
663 {
664         if (!_have_valid_pieces) {
665                 setup_pieces ();
666         }
667
668         list<shared_ptr<Font> > fonts;
669         BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
670                 if (p->content->subtitle) {
671                         /* XXX: things may go wrong if there are duplicate font IDs
672                            with different font files.
673                         */
674                         list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
675                         copy (f.begin(), f.end(), back_inserter (fonts));
676                 }
677         }
678
679         return fonts;
680 }
681
682 /** Set this player never to produce any video data */
683 void
684 Player::set_ignore_video ()
685 {
686         _ignore_video = true;
687 }
688
689 /** Set this player never to produce any audio data */
690 void
691 Player::set_ignore_audio ()
692 {
693         _ignore_audio = true;
694 }
695
696 /** Set whether or not this player should always burn text subtitles into the image,
697  *  regardless of the content settings.
698  *  @param burn true to always burn subtitles, false to obey content settings.
699  */
700 void
701 Player::set_always_burn_subtitles (bool burn)
702 {
703         _always_burn_subtitles = burn;
704 }
705
706 void
707 Player::set_fast ()
708 {
709         _fast = true;
710         _have_valid_pieces = false;
711 }
712
713 void
714 Player::set_play_referenced ()
715 {
716         _play_referenced = true;
717         _have_valid_pieces = false;
718 }
719
720 list<ReferencedReelAsset>
721 Player::get_reel_assets ()
722 {
723         list<ReferencedReelAsset> a;
724
725         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
726                 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
727                 if (!j) {
728                         continue;
729                 }
730
731                 scoped_ptr<DCPDecoder> decoder;
732                 try {
733                         decoder.reset (new DCPDecoder (j, _film->log()));
734                 } catch (...) {
735                         return a;
736                 }
737
738                 int64_t offset = 0;
739                 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
740                         DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
741                         if (j->reference_video ()) {
742                                 a.push_back (
743                                         ReferencedReelAsset (
744                                                 k->main_picture (),
745                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
746                                                 )
747                                         );
748                         }
749
750                         if (j->reference_audio ()) {
751                                 a.push_back (
752                                         ReferencedReelAsset (
753                                                 k->main_sound (),
754                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
755                                                 )
756                                         );
757                         }
758
759                         if (j->reference_subtitle ()) {
760                                 DCPOMATIC_ASSERT (k->main_subtitle ());
761                                 a.push_back (
762                                         ReferencedReelAsset (
763                                                 k->main_subtitle (),
764                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
765                                                 )
766                                         );
767                         }
768
769                         /* Assume that main picture duration is the length of the reel */
770                         offset += k->main_picture()->duration ();
771                 }
772         }
773
774         return a;
775 }
776
777 list<shared_ptr<Piece> >
778 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
779 {
780         if (!_have_valid_pieces) {
781                 setup_pieces ();
782         }
783
784         list<shared_ptr<Piece> > overlaps;
785         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
786                 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
787                         overlaps.push_back (i);
788                 }
789         }
790
791         return overlaps;
792 }
793
794 void
795 Player::reset_pieces ()
796 {
797         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
798                 i->decoder->reset ();
799         }
800 }