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