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