Fix exception when seeking with missing content.
[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 "player.h"
22 #include "film.h"
23 #include "ffmpeg_decoder.h"
24 #include "ffmpeg_content.h"
25 #include "image_decoder.h"
26 #include "image_content.h"
27 #include "sndfile_decoder.h"
28 #include "sndfile_content.h"
29 #include "subtitle_content.h"
30 #include "playlist.h"
31 #include "job.h"
32 #include "image.h"
33 #include "ratio.h"
34 #include "resampler.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
49 class Piece
50 {
51 public:
52         Piece (shared_ptr<Content> c)
53                 : content (c)
54                 , video_position (c->position ())
55                 , audio_position (c->position ())
56                 , repeat_to_do (0)
57                 , repeat_done (0)
58         {}
59         
60         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
61                 : content (c)
62                 , decoder (d)
63                 , video_position (c->position ())
64                 , audio_position (c->position ())
65                 , repeat_to_do (0)
66                 , repeat_done (0)
67         {}
68
69         /** Set this piece to repeat a video frame a given number of times */
70         void set_repeat (IncomingVideo video, int num)
71         {
72                 repeat_video = video;
73                 repeat_to_do = num;
74                 repeat_done = 0;
75         }
76
77         void reset_repeat ()
78         {
79                 repeat_video.image.reset ();
80                 repeat_to_do = 0;
81                 repeat_done = 0;
82         }
83
84         bool repeating () const
85         {
86                 return repeat_done != repeat_to_do;
87         }
88
89         void repeat (Player* player)
90         {
91                 player->process_video (
92                         repeat_video.weak_piece,
93                         repeat_video.image,
94                         repeat_video.eyes,
95                         repeat_done > 0,
96                         repeat_video.frame,
97                         (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
98                         );
99
100                 ++repeat_done;
101         }
102         
103         shared_ptr<Content> content;
104         shared_ptr<Decoder> decoder;
105         /** Time of the last video we emitted relative to the start of the DCP */
106         Time video_position;
107         /** Time of the last audio we emitted relative to the start of the DCP */
108         Time audio_position;
109
110         IncomingVideo repeat_video;
111         int repeat_to_do;
112         int repeat_done;
113 };
114
115 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
116         : _film (f)
117         , _playlist (p)
118         , _video (true)
119         , _audio (true)
120         , _have_valid_pieces (false)
121         , _video_position (0)
122         , _audio_position (0)
123         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
124         , _last_emit_was_black (false)
125 {
126         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
127         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
128         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
129         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
130 }
131
132 void
133 Player::disable_video ()
134 {
135         _video = false;
136 }
137
138 void
139 Player::disable_audio ()
140 {
141         _audio = false;
142 }
143
144 bool
145 Player::pass ()
146 {
147         if (!_have_valid_pieces) {
148                 setup_pieces ();
149         }
150
151         Time earliest_t = TIME_MAX;
152         shared_ptr<Piece> earliest;
153         enum {
154                 VIDEO,
155                 AUDIO
156         } type = VIDEO;
157
158         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
159                 if ((*i)->decoder->done ()) {
160                         continue;
161                 }
162
163                 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
164                 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
165
166                 if (_video && vd) {
167                         if ((*i)->video_position < earliest_t) {
168                                 earliest_t = (*i)->video_position;
169                                 earliest = *i;
170                                 type = VIDEO;
171                         }
172                 }
173
174                 if (_audio && ad && ad->has_audio ()) {
175                         if ((*i)->audio_position < earliest_t) {
176                                 earliest_t = (*i)->audio_position;
177                                 earliest = *i;
178                                 type = AUDIO;
179                         }
180                 }
181         }
182
183         if (!earliest) {
184                 flush ();
185                 return true;
186         }
187
188         switch (type) {
189         case VIDEO:
190                 if (earliest_t > _video_position) {
191                         emit_black ();
192                 } else {
193                         if (earliest->repeating ()) {
194                                 earliest->repeat (this);
195                         } else {
196                                 earliest->decoder->pass ();
197                         }
198                 }
199                 break;
200
201         case AUDIO:
202                 if (earliest_t > _audio_position) {
203                         emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
204                 } else {
205                         earliest->decoder->pass ();
206
207                         if (earliest->decoder->done()) {
208                                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
209                                 assert (ac);
210                                 shared_ptr<Resampler> re = resampler (ac, false);
211                                 if (re) {
212                                         shared_ptr<const AudioBuffers> b = re->flush ();
213                                         if (b->frames ()) {
214                                                 process_audio (earliest, b, ac->audio_length ());
215                                         }
216                                 }
217                         }
218                 }
219                 break;
220         }
221
222         if (_audio) {
223                 boost::optional<Time> audio_done_up_to;
224                 for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
225                         if ((*i)->decoder->done ()) {
226                                 continue;
227                         }
228
229                         shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
230                         if (ad && ad->has_audio ()) {
231                                 audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
232                         }
233                 }
234
235                 if (audio_done_up_to) {
236                         TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
237                         Audio (tb.audio, tb.time);
238                         _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
239                 }
240         }
241                 
242         return false;
243 }
244
245 /** @param extra Amount of extra time to add to the content frame's time (for repeat) */
246 void
247 Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
248 {
249         /* Keep a note of what came in so that we can repeat it if required */
250         _last_incoming_video.weak_piece = weak_piece;
251         _last_incoming_video.image = image;
252         _last_incoming_video.eyes = eyes;
253         _last_incoming_video.same = same;
254         _last_incoming_video.frame = frame;
255         _last_incoming_video.extra = extra;
256         
257         shared_ptr<Piece> piece = weak_piece.lock ();
258         if (!piece) {
259                 return;
260         }
261
262         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
263         assert (content);
264
265         FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
266         if (frc.skip && (frame % 2) == 1) {
267                 return;
268         }
269
270         Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
271         if (content->trimmed (relative_time)) {
272                 return;
273         }
274
275         Time const time = content->position() + relative_time + extra - content->trim_start ();
276         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
277         libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
278
279         shared_ptr<PlayerImage> pi (
280                 new PlayerImage (
281                         image,
282                         content->crop(),
283                         image_size,
284                         _video_container_size,
285                         _film->scaler()
286                         )
287                 );
288         
289         if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
290
291                 Position<int> const container_offset (
292                         (_video_container_size.width - image_size.width) / 2,
293                         (_video_container_size.height - image_size.width) / 2
294                         );
295
296                 pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
297         }
298                 
299                                             
300 #ifdef DCPOMATIC_DEBUG
301         _last_video = piece->content;
302 #endif
303
304         Video (pi, eyes, content->colour_conversion(), same, time);
305
306         _last_emit_was_black = false;
307         _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
308
309         if (frc.repeat > 1 && !piece->repeating ()) {
310                 piece->set_repeat (_last_incoming_video, frc.repeat - 1);
311         }
312 }
313
314 void
315 Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
316 {
317         shared_ptr<Piece> piece = weak_piece.lock ();
318         if (!piece) {
319                 return;
320         }
321
322         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
323         assert (content);
324
325         /* Gain */
326         if (content->audio_gain() != 0) {
327                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
328                 gain->apply_gain (content->audio_gain ());
329                 audio = gain;
330         }
331
332         /* Resample */
333         if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
334                 shared_ptr<Resampler> r = resampler (content, true);
335                 pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
336                 audio = ro.first;
337                 frame = ro.second;
338         }
339         
340         Time const relative_time = _film->audio_frames_to_time (frame);
341
342         if (content->trimmed (relative_time)) {
343                 return;
344         }
345
346         Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
347         
348         /* Remap channels */
349         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
350         dcp_mapped->make_silent ();
351
352         AudioMapping map = content->audio_mapping ();
353         for (int i = 0; i < map.content_channels(); ++i) {
354                 for (int j = 0; j < _film->audio_channels(); ++j) {
355                         if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
356                                 dcp_mapped->accumulate_channel (
357                                         audio.get(),
358                                         i,
359                                         static_cast<libdcp::Channel> (j),
360                                         map.get (i, static_cast<libdcp::Channel> (j))
361                                         );
362                         }
363                 }
364         }
365
366         audio = dcp_mapped;
367
368         /* We must cut off anything that comes before the start of all time */
369         if (time < 0) {
370                 int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
371                 if (frames >= audio->frames ()) {
372                         return;
373                 }
374
375                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
376                 trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
377
378                 audio = trimmed;
379                 time = 0;
380         }
381
382         _audio_merger.push (audio, time);
383         piece->audio_position += _film->audio_frames_to_time (audio->frames ());
384 }
385
386 void
387 Player::flush ()
388 {
389         TimedAudioBuffers<Time> tb = _audio_merger.flush ();
390         if (_audio && tb.audio) {
391                 Audio (tb.audio, tb.time);
392                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
393         }
394
395         while (_video && _video_position < _audio_position) {
396                 emit_black ();
397         }
398
399         while (_audio && _audio_position < _video_position) {
400                 emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
401         }
402         
403 }
404
405 /** Seek so that the next pass() will yield (approximately) the requested frame.
406  *  Pass accurate = true to try harder to get close to the request.
407  *  @return true on error
408  */
409 void
410 Player::seek (Time t, bool accurate)
411 {
412         if (!_have_valid_pieces) {
413                 setup_pieces ();
414         }
415
416         if (_pieces.empty ()) {
417                 return;
418         }
419
420         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
421                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
422                 if (!vc) {
423                         continue;
424                 }
425
426                 /* s is the offset of t from the start position of this content */
427                 Time s = t - vc->position ();
428                 s = max (static_cast<Time> (0), s);
429                 s = min (vc->length_after_trim(), s);
430
431                 /* Hence set the piece positions to the `global' time */
432                 (*i)->video_position = (*i)->audio_position = vc->position() + s;
433
434                 /* And seek the decoder */
435                 dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
436                         vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
437                         );
438
439                 (*i)->reset_repeat ();
440         }
441
442         _video_position = _audio_position = t;
443
444         /* XXX: don't seek audio because we don't need to... */
445 }
446
447 void
448 Player::setup_pieces ()
449 {
450         list<shared_ptr<Piece> > old_pieces = _pieces;
451
452         _pieces.clear ();
453
454         ContentList content = _playlist->content ();
455         sort (content.begin(), content.end(), ContentSorter ());
456
457         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
458
459                 if (!(*i)->paths_valid ()) {
460                         continue;
461                 }
462
463                 shared_ptr<Piece> piece (new Piece (*i));
464
465                 /* XXX: into content? */
466
467                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
468                 if (fc) {
469                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
470                         
471                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
472                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
473                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
474
475                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
476                         piece->decoder = fd;
477                 }
478                 
479                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
480                 if (ic) {
481                         bool reusing = false;
482                         
483                         /* See if we can re-use an old ImageDecoder */
484                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
485                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
486                                 if (imd && imd->content() == ic) {
487                                         piece = *j;
488                                         reusing = true;
489                                 }
490                         }
491
492                         if (!reusing) {
493                                 shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
494                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
495                                 piece->decoder = id;
496                         }
497                 }
498
499                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
500                 if (sc) {
501                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
502                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
503
504                         piece->decoder = sd;
505                 }
506
507                 _pieces.push_back (piece);
508         }
509
510         _have_valid_pieces = true;
511 }
512
513 void
514 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
515 {
516         shared_ptr<Content> c = w.lock ();
517         if (!c) {
518                 return;
519         }
520
521         if (
522                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
523                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
524                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
525                 ) {
526                 
527                 _have_valid_pieces = false;
528                 Changed (frequent);
529
530         } else if (
531                 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
532                 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
533                 property == SubtitleContentProperty::SUBTITLE_SCALE
534                 ) {
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 shared_ptr<Resampler>
579 Player::resampler (shared_ptr<AudioContent> c, bool create)
580 {
581         map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
582         if (i != _resamplers.end ()) {
583                 return i->second;
584         }
585
586         if (!create) {
587                 return shared_ptr<Resampler> ();
588         }
589
590         _film->log()->log (
591                 String::compose (
592                         "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
593                         )
594                 );
595         
596         shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
597         _resamplers[c] = r;
598         return r;
599 }
600
601 void
602 Player::emit_black ()
603 {
604 #ifdef DCPOMATIC_DEBUG
605         _last_video.reset ();
606 #endif
607
608         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
609         _video_position += _film->video_frames_to_time (1);
610         _last_emit_was_black = true;
611 }
612
613 void
614 Player::emit_silence (OutputAudioFrame most)
615 {
616         if (most == 0) {
617                 return;
618         }
619         
620         OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
621         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
622         silence->make_silent ();
623         Audio (silence, _audio_position);
624         _audio_position += _film->audio_frames_to_time (N);
625 }
626
627 void
628 Player::film_changed (Film::Property p)
629 {
630         /* Here we should notice Film properties that affect our output, and
631            alert listeners that our output now would be different to how it was
632            last time we were run.
633         */
634
635         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
636                 Changed (false);
637         }
638 }
639
640 void
641 Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
642 {
643         _in_subtitle.piece = weak_piece;
644         _in_subtitle.image = image;
645         _in_subtitle.rect = rect;
646         _in_subtitle.from = from;
647         _in_subtitle.to = to;
648
649         update_subtitle ();
650 }
651
652 void
653 Player::update_subtitle ()
654 {
655         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
656         if (!piece) {
657                 return;
658         }
659
660         if (!_in_subtitle.image) {
661                 _out_subtitle.image.reset ();
662                 return;
663         }
664
665         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
666         assert (sc);
667
668         dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
669         libdcp::Size scaled_size;
670
671         in_rect.x += sc->subtitle_x_offset ();
672         in_rect.y += sc->subtitle_y_offset ();
673
674         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
675         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
676         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
677
678         /* Then we need a corrective translation, consisting of two parts:
679          *
680          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
681          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
682          *
683          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
684          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
685          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
686          *
687          * Combining these two translations gives these expressions.
688          */
689         
690         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
691         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
692         
693         _out_subtitle.image = _in_subtitle.image->scale (
694                 scaled_size,
695                 Scaler::from_id ("bicubic"),
696                 _in_subtitle.image->pixel_format (),
697                 true
698                 );
699
700         /* XXX: hack */
701         Time from = _in_subtitle.from;
702         Time to = _in_subtitle.to;
703         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
704         if (vc) {
705                 from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
706                 to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
707         }
708         
709         _out_subtitle.from = from + piece->content->position ();
710         _out_subtitle.to = to + piece->content->position ();
711 }
712
713 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
714  *  @return false if this could not be done.
715  */
716 bool
717 Player::repeat_last_video ()
718 {
719         if (!_last_incoming_video.image || !_have_valid_pieces) {
720                 return false;
721         }
722
723         process_video (
724                 _last_incoming_video.weak_piece,
725                 _last_incoming_video.image,
726                 _last_incoming_video.eyes,
727                 _last_incoming_video.same,
728                 _last_incoming_video.frame,
729                 _last_incoming_video.extra
730                 );
731
732         return true;
733 }
734
735 PlayerImage::PlayerImage (
736         shared_ptr<const Image> in,
737         Crop crop,
738         libdcp::Size inter_size,
739         libdcp::Size out_size,
740         Scaler const * scaler
741         )
742         : _in (in)
743         , _crop (crop)
744         , _inter_size (inter_size)
745         , _out_size (out_size)
746         , _scaler (scaler)
747 {
748
749 }
750
751 void
752 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
753 {
754         _subtitle_image = image;
755         _subtitle_position = pos;
756 }
757
758 shared_ptr<Image>
759 PlayerImage::image ()
760 {
761         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
762
763         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
764
765         if (_subtitle_image) {
766                 out->alpha_blend (_subtitle_image, _subtitle_position);
767         }
768
769         return out;
770 }