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