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