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