Merge master.
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013 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 <stdint.h>
21 #include <algorithm>
22 #include "player.h"
23 #include "film.h"
24 #include "ffmpeg_decoder.h"
25 #include "ffmpeg_content.h"
26 #include "image_decoder.h"
27 #include "image_content.h"
28 #include "sndfile_decoder.h"
29 #include "sndfile_content.h"
30 #include "subtitle_content.h"
31 #include "playlist.h"
32 #include "job.h"
33 #include "image.h"
34 #include "ratio.h"
35 #include "log.h"
36 #include "scaler.h"
37
38 using std::list;
39 using std::cout;
40 using std::min;
41 using std::max;
42 using std::vector;
43 using std::pair;
44 using std::map;
45 using boost::shared_ptr;
46 using boost::weak_ptr;
47 using boost::dynamic_pointer_cast;
48 using boost::optional;
49
50 class Piece
51 {
52 public:
53         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
54                 : content (c)
55                 , decoder (d)
56                 , frc (f)
57         {}
58
59         shared_ptr<Content> content;
60         shared_ptr<Decoder> decoder;
61         FrameRateChange frc;
62 };
63
64 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
65         : _film (f)
66         , _playlist (p)
67         , _video (true)
68         , _audio (true)
69         , _have_valid_pieces (false)
70         , _video_position (0)
71         , _audio_position (0)
72         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
73         , _last_emit_was_black (false)
74         , _just_did_inaccurate_seek (false)
75         , _approximate_size (false)
76 {
77         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
78         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
79         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
80         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
81 }
82
83 void
84 Player::disable_video ()
85 {
86         _video = false;
87 }
88
89 void
90 Player::disable_audio ()
91 {
92         _audio = false;
93 }
94
95 bool
96 Player::pass ()
97 {
98         if (!_have_valid_pieces) {
99                 setup_pieces ();
100         }
101
102         /* Interrogate all our pieces to find the one with the earliest decoded data */
103
104         shared_ptr<Piece> earliest_piece;
105         shared_ptr<Decoded> earliest_decoded;
106         DCPTime earliest_time = TIME_MAX;
107         DCPTime earliest_audio = TIME_MAX;
108
109         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
110
111                 DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
112                 
113                 bool done = false;
114                 shared_ptr<Decoded> dec;
115                 while (!done) {
116                         dec = (*i)->decoder->peek ();
117                         if (!dec) {
118                                 /* Decoder has nothing else to give us */
119                                 break;
120                         }
121
122                         dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
123                         DCPTime const t = dec->dcp_time - offset;
124                         if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
125                                 /* In the end-trimmed part; decoder has nothing else to give us */
126                                 dec.reset ();
127                                 done = true;
128                         } else if (t >= (*i)->content->trim_start ()) {
129                                 /* Within the un-trimmed part; everything's ok */
130                                 done = true;
131                         } else {
132                                 /* Within the start-trimmed part; get something else */
133                                 (*i)->decoder->consume ();
134                         }
135                 }
136
137                 if (!dec) {
138                         continue;
139                 }
140
141                 if (dec->dcp_time < earliest_time) {
142                         earliest_piece = *i;
143                         earliest_decoded = dec;
144                         earliest_time = dec->dcp_time;
145                 }
146
147                 if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
148                         earliest_audio = dec->dcp_time;
149                 }
150         }
151                 
152         if (!earliest_piece) {
153                 flush ();
154                 return true;
155         }
156
157         if (earliest_audio != TIME_MAX) {
158                 TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
159                 Audio (tb.audio, tb.time);
160                 /* This assumes that the audio_frames_to_time conversion is exact
161                    so that there are no accumulated errors caused by rounding.
162                 */
163                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
164         }
165
166         /* Emit the earliest thing */
167
168         shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
169         shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
170         shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
171
172         /* Will be set to false if we shouldn't consume the peeked DecodedThing */
173         bool consume = true;
174
175         if (dv && _video) {
176
177                 if (_just_did_inaccurate_seek) {
178
179                         /* Just emit; no subtlety */
180                         emit_video (earliest_piece, dv);
181                         step_video_position (dv);
182                         
183                 } else if (dv->dcp_time > _video_position) {
184
185                         /* Too far ahead */
186
187                         list<shared_ptr<Piece> >::iterator i = _pieces.begin();
188                         while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
189                                 ++i;
190                         }
191
192                         if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
193                                 /* We're outside all video content */
194                                 emit_black ();
195                                 _statistics.video.black++;
196                         } else {
197                                 /* We're inside some video; repeat the frame */
198                                 _last_incoming_video.video->dcp_time = _video_position;
199                                 emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
200                                 step_video_position (_last_incoming_video.video);
201                                 _statistics.video.repeat++;
202                         }
203
204                         consume = false;
205
206                 } else if (dv->dcp_time == _video_position) {
207                         /* We're ok */
208                         emit_video (earliest_piece, dv);
209                         step_video_position (dv);
210                         _statistics.video.good++;
211                 } else {
212                         /* Too far behind: skip */
213                         _statistics.video.skip++;
214                 }
215
216                 _just_did_inaccurate_seek = false;
217
218         } else if (da && _audio) {
219
220                 if (da->dcp_time > _audio_position) {
221                         /* Too far ahead */
222                         emit_silence (da->dcp_time - _audio_position);
223                         consume = false;
224                         _statistics.audio.silence += (da->dcp_time - _audio_position);
225                 } else if (da->dcp_time == _audio_position) {
226                         /* We're ok */
227                         emit_audio (earliest_piece, da);
228                         _statistics.audio.good += da->data->frames();
229                 } else {
230                         /* Too far behind: skip */
231                         _statistics.audio.skip += da->data->frames();
232                 }
233                 
234         } else if (ds && _video) {
235                 _in_subtitle.piece = earliest_piece;
236                 _in_subtitle.subtitle = ds;
237                 update_subtitle ();
238         }
239
240         if (consume) {
241                 earliest_piece->decoder->consume ();
242         }                       
243         
244         return false;
245 }
246
247 void
248 Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
249 {
250         /* Keep a note of what came in so that we can repeat it if required */
251         _last_incoming_video.weak_piece = weak_piece;
252         _last_incoming_video.video = video;
253         
254         shared_ptr<Piece> piece = weak_piece.lock ();
255         if (!piece) {
256                 return;
257         }
258
259         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
260         assert (content);
261
262         FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
263
264         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
265         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
266         if (_approximate_size) {
267                 image_size.width &= ~3;
268                 image_size.height &= ~3;
269         }
270
271         shared_ptr<PlayerImage> pi (
272                 new PlayerImage (
273                         video->image,
274                         content->crop(),
275                         image_size,
276                         _video_container_size,
277                         _film->scaler()
278                         )
279                 );
280         
281         if (
282                 _film->with_subtitles () &&
283                 _out_subtitle.image &&
284                 video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
285                 ) {
286
287                 Position<int> const container_offset (
288                         (_video_container_size.width - image_size.width) / 2,
289                         (_video_container_size.height - image_size.height) / 2
290                         );
291
292                 pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
293         }
294                 
295                                             
296 #ifdef DCPOMATIC_DEBUG
297         _last_video = piece->content;
298 #endif
299
300         Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
301         
302         _last_emit_was_black = false;
303 }
304
305 void
306 Player::step_video_position (shared_ptr<DecodedVideo> video)
307 {
308         /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
309         if (video->eyes != EYES_LEFT) {
310                 /* This assumes that the video_frames_to_time conversion is exact
311                    so that there are no accumulated errors caused by rounding.
312                 */
313                 _video_position += _film->video_frames_to_time (1);
314         }
315 }
316
317 void
318 Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
319 {
320         shared_ptr<Piece> piece = weak_piece.lock ();
321         if (!piece) {
322                 return;
323         }
324
325         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
326         assert (content);
327
328         /* Gain */
329         if (content->audio_gain() != 0) {
330                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
331                 gain->apply_gain (content->audio_gain ());
332                 audio->data = gain;
333         }
334
335         /* Remap channels */
336         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
337         dcp_mapped->make_silent ();
338         AudioMapping map = content->audio_mapping ();
339         for (int i = 0; i < map.content_channels(); ++i) {
340                 for (int j = 0; j < _film->audio_channels(); ++j) {
341                         if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
342                                 dcp_mapped->accumulate_channel (
343                                         audio->data.get(),
344                                         i,
345                                         static_cast<libdcp::Channel> (j),
346                                         map.get (i, static_cast<libdcp::Channel> (j))
347                                         );
348                         }
349                 }
350         }
351
352         audio->data = dcp_mapped;
353
354         /* Delay */
355         audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
356         if (audio->dcp_time < 0) {
357                 int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
358                 if (frames >= audio->data->frames ()) {
359                         return;
360                 }
361
362                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
363                 trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
364
365                 audio->data = trimmed;
366                 audio->dcp_time = 0;
367         }
368
369         _audio_merger.push (audio->data, audio->dcp_time);
370 }
371
372 void
373 Player::flush ()
374 {
375         TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
376         if (_audio && tb.audio) {
377                 Audio (tb.audio, tb.time);
378                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
379         }
380
381         while (_video && _video_position < _audio_position) {
382                 emit_black ();
383         }
384
385         while (_audio && _audio_position < _video_position) {
386                 emit_silence (_video_position - _audio_position);
387         }
388         
389 }
390
391 /** Seek so that the next pass() will yield (approximately) the requested frame.
392  *  Pass accurate = true to try harder to get close to the request.
393  *  @return true on error
394  */
395 void
396 Player::seek (DCPTime t, bool accurate)
397 {
398         if (!_have_valid_pieces) {
399                 setup_pieces ();
400         }
401
402         if (_pieces.empty ()) {
403                 return;
404         }
405
406         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
407                 /* s is the offset of t from the start position of this content */
408                 DCPTime s = t - (*i)->content->position ();
409                 s = max (static_cast<DCPTime> (0), s);
410                 s = min ((*i)->content->length_after_trim(), s);
411
412                 /* Convert this to the content time */
413                 ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
414
415                 /* And seek the decoder */
416                 (*i)->decoder->seek (ct, accurate);
417         }
418
419         _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
420         _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
421
422         _audio_merger.clear (_audio_position);
423
424         if (!accurate) {
425                 /* We just did an inaccurate seek, so it's likely that the next thing seen
426                    out of pass() will be a fair distance from _{video,audio}_position.  Setting
427                    this flag stops pass() from trying to fix that: we assume that if it
428                    was an inaccurate seek then the caller does not care too much about
429                    inserting black/silence to keep the time tidy.
430                 */
431                 _just_did_inaccurate_seek = true;
432         }
433 }
434
435 void
436 Player::setup_pieces ()
437 {
438         list<shared_ptr<Piece> > old_pieces = _pieces;
439         _pieces.clear ();
440
441         ContentList content = _playlist->content ();
442
443         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
444
445                 shared_ptr<Decoder> decoder;
446                 optional<FrameRateChange> frc;
447
448                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
449                 if (fc) {
450                         decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
451                         frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
452                 }
453                 
454                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
455                 if (ic) {
456                         /* See if we can re-use an old ImageDecoder */
457                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
458                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
459                                 if (imd && imd->content() == ic) {
460                                         decoder = imd;
461                                 }
462                         }
463
464                         if (!decoder) {
465                                 decoder.reset (new ImageDecoder (_film, ic));
466                         }
467
468                         frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
469                 }
470
471                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
472                 if (sc) {
473                         decoder.reset (new SndfileDecoder (_film, sc));
474
475                         /* Working out the frc for this content is a bit tricky: what if it overlaps
476                            two pieces of video content with different frame rates?  For now, use
477                            the one with the best overlap.
478                         */
479
480                         DCPTime best_overlap_t = 0;
481                         shared_ptr<VideoContent> best_overlap;
482                         for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
483                                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
484                                 if (!vc) {
485                                         continue;
486                                 }
487
488                                 DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
489                                 if (overlap > best_overlap_t) {
490                                         best_overlap = vc;
491                                         best_overlap_t = overlap;
492                                 }
493                         }
494
495                         if (best_overlap) {
496                                 frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
497                         } else {
498                                 /* No video overlap; e.g. if the DCP is just audio */
499                                 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
500                         }
501                 }
502
503                 ContentTime st = (*i)->trim_start() * frc->speed_up;
504                 decoder->seek (st, true);
505                 
506                 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
507         }
508
509         _have_valid_pieces = true;
510
511         /* The Piece for the _last_incoming_video will no longer be valid */
512         _last_incoming_video.video.reset ();
513
514         _video_position = _audio_position = 0;
515 }
516
517 void
518 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
519 {
520         shared_ptr<Content> c = w.lock ();
521         if (!c) {
522                 return;
523         }
524
525         if (
526                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
527                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
528                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
529                 ) {
530                 
531                 _have_valid_pieces = false;
532                 Changed (frequent);
533
534         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
535
536                 update_subtitle ();
537                 Changed (frequent);
538
539         } else if (
540                 property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO ||
541                 property == VideoContentProperty::VIDEO_FRAME_RATE
542                 ) {
543                 
544                 Changed (frequent);
545
546         } else if (property == ContentProperty::PATH) {
547
548                 Changed (frequent);
549         }
550 }
551
552 void
553 Player::playlist_changed ()
554 {
555         _have_valid_pieces = false;
556         Changed (false);
557 }
558
559 void
560 Player::set_video_container_size (libdcp::Size s)
561 {
562         _video_container_size = s;
563
564         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
565         im->make_black ();
566         
567         _black_frame.reset (
568                 new PlayerImage (
569                         im,
570                         Crop(),
571                         _video_container_size,
572                         _video_container_size,
573                         Scaler::from_id ("bicubic")
574                         )
575                 );
576 }
577
578 void
579 Player::emit_black ()
580 {
581 #ifdef DCPOMATIC_DEBUG
582         _last_video.reset ();
583 #endif
584
585         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
586         _video_position += _film->video_frames_to_time (1);
587         _last_emit_was_black = true;
588 }
589
590 void
591 Player::emit_silence (DCPTime most)
592 {
593         if (most == 0) {
594                 return;
595         }
596         
597         DCPTime t = min (most, TIME_HZ / 2);
598         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
599         silence->make_silent ();
600         Audio (silence, _audio_position);
601         
602         _audio_position += t;
603 }
604
605 void
606 Player::film_changed (Film::Property p)
607 {
608         /* Here we should notice Film properties that affect our output, and
609            alert listeners that our output now would be different to how it was
610            last time we were run.
611         */
612
613         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
614                 Changed (false);
615         }
616 }
617
618 void
619 Player::update_subtitle ()
620 {
621         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
622         if (!piece) {
623                 return;
624         }
625
626         if (!_in_subtitle.subtitle->image) {
627                 _out_subtitle.image.reset ();
628                 return;
629         }
630
631         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
632         assert (sc);
633
634         dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
635         libdcp::Size scaled_size;
636
637         in_rect.y += sc->subtitle_offset ();
638
639         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
640         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
641         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
642
643         /* Then we need a corrective translation, consisting of two parts:
644          *
645          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
646          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
647          *
648          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
649          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
650          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
651          *
652          * Combining these two translations gives these expressions.
653          */
654         
655         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
656         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
657
658         _out_subtitle.image = _in_subtitle.subtitle->image->scale (
659                 scaled_size,
660                 Scaler::from_id ("bicubic"),
661                 PIX_FMT_RGBA,
662                 true
663                 );
664
665 <<<<<<< HEAD
666         _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
667         _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
668 =======
669         /* XXX: hack */
670         Time from = _in_subtitle.from;
671         Time to = _in_subtitle.to;
672         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
673         if (vc) {
674                 from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
675                 to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
676         }
677         
678         _out_subtitle.from = from * piece->content->position ();
679         _out_subtitle.to = to + piece->content->position ();
680 >>>>>>> master
681 }
682
683 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
684  *  @return false if this could not be done.
685  */
686 bool
687 Player::repeat_last_video ()
688 {
689         if (!_last_incoming_video.video || !_have_valid_pieces) {
690                 return false;
691         }
692
693         emit_video (
694                 _last_incoming_video.weak_piece,
695                 _last_incoming_video.video
696                 );
697
698         return true;
699 }
700
701 void
702 Player::set_approximate_size ()
703 {
704         _approximate_size = true;
705 }
706                               
707
708 PlayerImage::PlayerImage (
709         shared_ptr<const Image> in,
710         Crop crop,
711         libdcp::Size inter_size,
712         libdcp::Size out_size,
713         Scaler const * scaler
714         )
715         : _in (in)
716         , _crop (crop)
717         , _inter_size (inter_size)
718         , _out_size (out_size)
719         , _scaler (scaler)
720 {
721
722 }
723
724 void
725 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
726 {
727         _subtitle_image = image;
728         _subtitle_position = pos;
729 }
730
731 shared_ptr<Image>
732 PlayerImage::image (AVPixelFormat format, bool aligned)
733 {
734         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
735         
736         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
737
738         if (_subtitle_image) {
739                 out->alpha_blend (_subtitle_image, _subtitle_position);
740         }
741
742         return out;
743 }
744
745 void
746 PlayerStatistics::dump (shared_ptr<Log> log) const
747 {
748         log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
749         log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
750 }
751
752 PlayerStatistics const &
753 Player::statistics () const
754 {
755         return _statistics;
756 }