Attempt to tidy up internal APIs slightly.
[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 () const
290 {
291         return shared_ptr<PlayerVideo> (
292                 new PlayerVideo (
293                         shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
294                         Crop (),
295                         optional<double> (),
296                         _video_container_size,
297                         _video_container_size,
298                         EYES_BOTH,
299                         PART_WHOLE,
300                         PresetColourConversion::all().front().conversion
301                 )
302         );
303 }
304
305 Frame
306 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
307 {
308         DCPTime s = t - piece->content->position ();
309         s = min (piece->content->length_after_trim(), s);
310         s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
311
312         /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
313            then convert that ContentTime to frames at the content's rate.  However this fails for
314            situations like content at 29.9978733fps, DCP at 30fps.  The accuracy of the Time type is not
315            enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
316
317            Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
318         */
319         return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
320 }
321
322 DCPTime
323 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
324 {
325         /* See comment in dcp_to_content_video */
326         DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
327         return max (DCPTime (), d + piece->content->position ());
328 }
329
330 Frame
331 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
332 {
333         DCPTime s = t - piece->content->position ();
334         s = min (piece->content->length_after_trim(), s);
335         /* See notes in dcp_to_content_video */
336         return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
337 }
338
339 DCPTime
340 Player::resampled_audio_to_dcp (shared_ptr<const Piece> piece, Frame f) const
341 {
342         /* See comment in dcp_to_content_video */
343         DCPTime const d = DCPTime::from_frames (f, _film->audio_frame_rate()) - DCPTime (piece->content->trim_start (), piece->frc);
344         return max (DCPTime (), d + piece->content->position ());
345 }
346
347 ContentTime
348 Player::dcp_to_content_time (shared_ptr<const Piece> piece, DCPTime t) const
349 {
350         DCPTime s = t - piece->content->position ();
351         s = min (piece->content->length_after_trim(), s);
352         return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
353 }
354
355 DCPTime
356 Player::content_time_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
357 {
358         return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
359 }
360
361 list<shared_ptr<Font> >
362 Player::get_subtitle_fonts ()
363 {
364         if (!_have_valid_pieces) {
365                 setup_pieces ();
366         }
367
368         list<shared_ptr<Font> > fonts;
369         BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
370                 if (p->content->subtitle) {
371                         /* XXX: things may go wrong if there are duplicate font IDs
372                            with different font files.
373                         */
374                         list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
375                         copy (f.begin(), f.end(), back_inserter (fonts));
376                 }
377         }
378
379         return fonts;
380 }
381
382 /** Set this player never to produce any video data */
383 void
384 Player::set_ignore_video ()
385 {
386         _ignore_video = true;
387 }
388
389 /** Set this player never to produce any audio data */
390 void
391 Player::set_ignore_audio ()
392 {
393         _ignore_audio = true;
394 }
395
396 /** Set whether or not this player should always burn text subtitles into the image,
397  *  regardless of the content settings.
398  *  @param burn true to always burn subtitles, false to obey content settings.
399  */
400 void
401 Player::set_always_burn_subtitles (bool burn)
402 {
403         _always_burn_subtitles = burn;
404 }
405
406 void
407 Player::set_fast ()
408 {
409         _fast = true;
410         _have_valid_pieces = false;
411 }
412
413 void
414 Player::set_play_referenced ()
415 {
416         _play_referenced = true;
417         _have_valid_pieces = false;
418 }
419
420 list<ReferencedReelAsset>
421 Player::get_reel_assets ()
422 {
423         list<ReferencedReelAsset> a;
424
425         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
426                 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
427                 if (!j) {
428                         continue;
429                 }
430
431                 scoped_ptr<DCPDecoder> decoder;
432                 try {
433                         decoder.reset (new DCPDecoder (j, _film->log()));
434                 } catch (...) {
435                         return a;
436                 }
437
438                 int64_t offset = 0;
439                 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
440
441                         DCPOMATIC_ASSERT (j->video_frame_rate ());
442                         double const cfr = j->video_frame_rate().get();
443                         Frame const trim_start = j->trim_start().frames_round (cfr);
444                         Frame const trim_end = j->trim_end().frames_round (cfr);
445                         int const ffr = _film->video_frame_rate ();
446
447                         DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
448                         if (j->reference_video ()) {
449                                 shared_ptr<dcp::ReelAsset> ra = k->main_picture ();
450                                 DCPOMATIC_ASSERT (ra);
451                                 ra->set_entry_point (ra->entry_point() + trim_start);
452                                 ra->set_duration (ra->duration() - trim_start - trim_end);
453                                 a.push_back (
454                                         ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
455                                         );
456                         }
457
458                         if (j->reference_audio ()) {
459                                 shared_ptr<dcp::ReelAsset> ra = k->main_sound ();
460                                 DCPOMATIC_ASSERT (ra);
461                                 ra->set_entry_point (ra->entry_point() + trim_start);
462                                 ra->set_duration (ra->duration() - trim_start - trim_end);
463                                 a.push_back (
464                                         ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
465                                         );
466                         }
467
468                         if (j->reference_subtitle ()) {
469                                 shared_ptr<dcp::ReelAsset> ra = k->main_subtitle ();
470                                 DCPOMATIC_ASSERT (ra);
471                                 ra->set_entry_point (ra->entry_point() + trim_start);
472                                 ra->set_duration (ra->duration() - trim_start - trim_end);
473                                 a.push_back (
474                                         ReferencedReelAsset (ra, DCPTimePeriod (from, from + DCPTime::from_frames (ra->duration(), ffr)))
475                                         );
476                         }
477
478                         /* Assume that main picture duration is the length of the reel */
479                         offset += k->main_picture()->duration ();
480                 }
481         }
482
483         return a;
484 }
485
486 list<shared_ptr<Piece> >
487 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
488 {
489         if (!_have_valid_pieces) {
490                 setup_pieces ();
491         }
492
493         list<shared_ptr<Piece> > overlaps;
494         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
495                 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
496                         overlaps.push_back (i);
497                 }
498         }
499
500         return overlaps;
501 }
502
503 bool
504 Player::pass ()
505 {
506         if (!_have_valid_pieces) {
507                 setup_pieces ();
508         }
509
510         shared_ptr<Piece> earliest;
511         DCPTime earliest_position;
512         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
513                 DCPTime const t = i->content->position() + DCPTime (i->decoder->position(), i->frc);
514                 if (!earliest || t < earliest_position) {
515                         earliest_position = t;
516                         earliest = i;
517                 }
518         }
519
520         if (!earliest) {
521                 return true;
522         }
523
524         cout << "Pass " << earliest->content->path(0) << "\n";
525         earliest->decoder->pass ();
526
527         /* Emit any audio that is ready */
528
529         pair<shared_ptr<AudioBuffers>, DCPTime> audio = _audio_merger.pull (earliest_position);
530         if (audio.first->frames() > 0) {
531                 DCPOMATIC_ASSERT (audio.second >= _last_audio_time);
532                 DCPTime t = _last_audio_time;
533                 while (t < audio.second) {
534                         /* Silence up to the time of this new audio */
535                         DCPTime block = min (DCPTime::from_seconds (0.5), audio.second - t);
536                         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), block.frames_round(_film->audio_frame_rate())));
537                         silence->make_silent ();
538                         Audio (silence, t);
539                         t += block;
540                 }
541
542                 Audio (audio.first, audio.second);
543                 _last_audio_time = audio.second;
544         }
545
546         return false;
547 }
548
549 void
550 Player::video (weak_ptr<Piece> wp, ContentVideo video)
551 {
552         shared_ptr<Piece> piece = wp.lock ();
553         if (!piece) {
554                 return;
555         }
556
557         /* Time and period of the frame we will emit */
558         DCPTime const time = content_video_to_dcp (piece, video.frame.index());
559         DCPTimePeriod const period (time, time + DCPTime::from_frames (1, _film->video_frame_rate()));
560
561         /* Get any subtitles */
562
563         optional<PositionImage> subtitles;
564
565         for (list<pair<PlayerSubtitles, DCPTimePeriod> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
566
567                 if (!i->second.overlap (period)) {
568                         continue;
569                 }
570
571                 list<PositionImage> sub_images;
572
573                 /* Image subtitles */
574                 list<PositionImage> c = transform_image_subtitles (i->first.image);
575                 copy (c.begin(), c.end(), back_inserter (sub_images));
576
577                 /* Text subtitles (rendered to an image) */
578                 if (!i->first.text.empty ()) {
579                         list<PositionImage> s = render_subtitles (i->first.text, i->first.fonts, _video_container_size, time);
580                         copy (s.begin (), s.end (), back_inserter (sub_images));
581                 }
582
583                 if (!sub_images.empty ()) {
584                         subtitles = merge (sub_images);
585                 }
586         }
587
588         /* Fill gaps */
589
590         if (_last_video_time) {
591                 for (DCPTime i = _last_video_time.get(); i < time; i += DCPTime::from_frames (1, _film->video_frame_rate())) {
592                         if (_playlist->video_content_at(i) && _last_video) {
593                                 Video (shared_ptr<PlayerVideo> (new PlayerVideo (*_last_video)), i);
594                         } else {
595                                 Video (black_player_video_frame (), i);
596                         }
597                 }
598         }
599
600         _last_video.reset (
601                 new PlayerVideo (
602                         video.image,
603                         piece->content->video->crop (),
604                         piece->content->video->fade (video.frame.index()),
605                         piece->content->video->scale().size (
606                                 piece->content->video, _video_container_size, _film->frame_size ()
607                                 ),
608                         _video_container_size,
609                         video.frame.eyes(),
610                         video.part,
611                         piece->content->video->colour_conversion ()
612                         )
613                 );
614
615         if (subtitles) {
616                 _last_video->set_subtitle (subtitles.get ());
617         }
618
619         _last_video_time = time;
620
621         cout << "Video @ " << to_string(_last_video_time.get()) << "\n";
622         Video (_last_video, *_last_video_time);
623
624         /* Discard any subtitles we no longer need */
625
626         for (list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator i = _subtitles.begin (); i != _subtitles.end(); ) {
627                 list<pair<PlayerSubtitles, DCPTimePeriod> >::iterator tmp = i;
628                 ++tmp;
629
630                 if (i->second.to < time) {
631                         _subtitles.erase (i);
632                 }
633
634                 i = tmp;
635         }
636 }
637
638 void
639 Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_audio)
640 {
641         shared_ptr<Piece> piece = wp.lock ();
642         if (!piece) {
643                 return;
644         }
645
646         shared_ptr<AudioContent> content = piece->content->audio;
647         DCPOMATIC_ASSERT (content);
648
649         shared_ptr<AudioBuffers> audio = content_audio.audio;
650
651         /* Gain */
652         if (content->gain() != 0) {
653                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
654                 gain->apply_gain (content->gain ());
655                 audio = gain;
656         }
657
658         /* XXX: end-trimming used to be checked here */
659
660         /* Compute time in the DCP */
661         DCPTime const time = resampled_audio_to_dcp (piece, content_audio.frame) + DCPTime::from_seconds (content->delay() / 1000);
662
663         /* Remap channels */
664         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
665         dcp_mapped->make_silent ();
666
667         AudioMapping map = stream->mapping ();
668         for (int i = 0; i < map.input_channels(); ++i) {
669                 for (int j = 0; j < dcp_mapped->channels(); ++j) {
670                         if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
671                                 dcp_mapped->accumulate_channel (
672                                         audio.get(),
673                                         i,
674                                         static_cast<dcp::Channel> (j),
675                                         map.get (i, static_cast<dcp::Channel> (j))
676                                         );
677                         }
678                 }
679         }
680
681         audio = dcp_mapped;
682
683         if (_audio_processor) {
684                 audio = _audio_processor->run (audio, _film->audio_channels ());
685         }
686
687         _audio_merger.push (audio, time);
688 }
689
690 void
691 Player::image_subtitle (weak_ptr<Piece> wp, ContentImageSubtitle subtitle)
692 {
693         shared_ptr<Piece> piece = wp.lock ();
694         if (!piece) {
695                 return;
696         }
697
698         /* Apply content's subtitle offsets */
699         subtitle.sub.rectangle.x += piece->content->subtitle->x_offset ();
700         subtitle.sub.rectangle.y += piece->content->subtitle->y_offset ();
701
702         /* Apply content's subtitle scale */
703         subtitle.sub.rectangle.width *= piece->content->subtitle->x_scale ();
704         subtitle.sub.rectangle.height *= piece->content->subtitle->y_scale ();
705
706         /* Apply a corrective translation to keep the subtitle centred after that scale */
707         subtitle.sub.rectangle.x -= subtitle.sub.rectangle.width * (piece->content->subtitle->x_scale() - 1);
708         subtitle.sub.rectangle.y -= subtitle.sub.rectangle.height * (piece->content->subtitle->y_scale() - 1);
709
710         PlayerSubtitles ps;
711         ps.image.push_back (subtitle.sub);
712         DCPTimePeriod period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
713
714         if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
715                 _subtitles.push_back (make_pair (ps, period));
716         } else {
717                 Subtitle (ps, period);
718         }
719 }
720
721 void
722 Player::text_subtitle (weak_ptr<Piece> wp, ContentTextSubtitle subtitle)
723 {
724         shared_ptr<Piece> piece = wp.lock ();
725         if (!piece) {
726                 return;
727         }
728
729         PlayerSubtitles ps;
730         DCPTimePeriod const period (content_time_to_dcp (piece, subtitle.period().from), content_time_to_dcp (piece, subtitle.period().to));
731
732         BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
733                 s.set_h_position (s.h_position() + piece->content->subtitle->x_offset ());
734                 s.set_v_position (s.v_position() + piece->content->subtitle->y_offset ());
735                 float const xs = piece->content->subtitle->x_scale();
736                 float const ys = piece->content->subtitle->y_scale();
737                 float size = s.size();
738
739                 /* Adjust size to express the common part of the scaling;
740                    e.g. if xs = ys = 0.5 we scale size by 2.
741                 */
742                 if (xs > 1e-5 && ys > 1e-5) {
743                         size *= 1 / min (1 / xs, 1 / ys);
744                 }
745                 s.set_size (size);
746
747                 /* Then express aspect ratio changes */
748                 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
749                         s.set_aspect_adjust (xs / ys);
750                 }
751
752                 s.set_in (dcp::Time(period.from.seconds(), 1000));
753                 s.set_out (dcp::Time(period.to.seconds(), 1000));
754                 ps.text.push_back (SubtitleString (s, piece->content->subtitle->outline_width()));
755                 ps.add_fonts (piece->content->subtitle->fonts ());
756         }
757
758         if (piece->content->subtitle->use() && (piece->content->subtitle->burn() || _always_burn_subtitles)) {
759                 _subtitles.push_back (make_pair (ps, period));
760         } else {
761                 Subtitle (ps, period);
762         }
763 }
764
765 void
766 Player::seek (DCPTime time, bool accurate)
767 {
768         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
769                 if (i->content->position() <= time && time < i->content->end()) {
770                         i->decoder->seek (dcp_to_content_time (i, time), accurate);
771                 }
772         }
773
774         if (accurate) {
775                 _last_video_time = time - DCPTime::from_frames (1, _film->video_frame_rate ());
776         } else {
777                 _last_video_time = optional<DCPTime> ();
778         }
779 }