Merge.
[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                 shared_ptr<Piece> piece (new Piece (*i));
460
461                 /* XXX: into content? */
462
463                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
464                 if (fc) {
465                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
466                         
467                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
468                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
469                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
470
471                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
472                         piece->decoder = fd;
473                 }
474                 
475                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
476                 if (ic) {
477                         bool reusing = false;
478                         
479                         /* See if we can re-use an old ImageDecoder */
480                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
481                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
482                                 if (imd && imd->content() == ic) {
483                                         piece = *j;
484                                         reusing = true;
485                                 }
486                         }
487
488                         if (!reusing) {
489                                 shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
490                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
491                                 piece->decoder = id;
492                         }
493                 }
494
495                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
496                 if (sc) {
497                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
498                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
499
500                         piece->decoder = sd;
501                 }
502
503                 _pieces.push_back (piece);
504         }
505
506         _have_valid_pieces = true;
507 }
508
509 void
510 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
511 {
512         shared_ptr<Content> c = w.lock ();
513         if (!c) {
514                 return;
515         }
516
517         if (
518                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
519                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
520                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
521                 ) {
522                 
523                 _have_valid_pieces = false;
524                 Changed (frequent);
525
526         } else if (
527                 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
528                 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
529                 property == SubtitleContentProperty::SUBTITLE_SCALE
530                 ) {
531
532                 update_subtitle ();
533                 Changed (frequent);
534
535         } else if (
536                 property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO ||
537                 property == VideoContentProperty::VIDEO_FRAME_RATE
538                 ) {
539                 
540                 Changed (frequent);
541
542         } else if (property == ContentProperty::PATH) {
543
544                 Changed (frequent);
545         }
546 }
547
548 void
549 Player::playlist_changed ()
550 {
551         _have_valid_pieces = false;
552         Changed (false);
553 }
554
555 void
556 Player::set_video_container_size (libdcp::Size s)
557 {
558         _video_container_size = s;
559
560         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
561         im->make_black ();
562         
563         _black_frame.reset (
564                 new PlayerImage (
565                         im,
566                         Crop(),
567                         _video_container_size,
568                         _video_container_size,
569                         Scaler::from_id ("bicubic")
570                         )
571                 );
572 }
573
574 shared_ptr<Resampler>
575 Player::resampler (shared_ptr<AudioContent> c, bool create)
576 {
577         map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
578         if (i != _resamplers.end ()) {
579                 return i->second;
580         }
581
582         if (!create) {
583                 return shared_ptr<Resampler> ();
584         }
585
586         _film->log()->log (
587                 String::compose (
588                         "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
589                         )
590                 );
591         
592         shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
593         _resamplers[c] = r;
594         return r;
595 }
596
597 void
598 Player::emit_black ()
599 {
600 #ifdef DCPOMATIC_DEBUG
601         _last_video.reset ();
602 #endif
603
604         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
605         _video_position += _film->video_frames_to_time (1);
606         _last_emit_was_black = true;
607 }
608
609 void
610 Player::emit_silence (OutputAudioFrame most)
611 {
612         if (most == 0) {
613                 return;
614         }
615         
616         OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
617         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
618         silence->make_silent ();
619         Audio (silence, _audio_position);
620         _audio_position += _film->audio_frames_to_time (N);
621 }
622
623 void
624 Player::film_changed (Film::Property p)
625 {
626         /* Here we should notice Film properties that affect our output, and
627            alert listeners that our output now would be different to how it was
628            last time we were run.
629         */
630
631         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
632                 Changed (false);
633         }
634 }
635
636 void
637 Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
638 {
639         _in_subtitle.piece = weak_piece;
640         _in_subtitle.image = image;
641         _in_subtitle.rect = rect;
642         _in_subtitle.from = from;
643         _in_subtitle.to = to;
644
645         update_subtitle ();
646 }
647
648 void
649 Player::update_subtitle ()
650 {
651         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
652         if (!piece) {
653                 return;
654         }
655
656         if (!_in_subtitle.image) {
657                 _out_subtitle.image.reset ();
658                 return;
659         }
660
661         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
662         assert (sc);
663
664         dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
665         libdcp::Size scaled_size;
666
667         in_rect.x += sc->subtitle_x_offset ();
668         in_rect.y += sc->subtitle_y_offset ();
669
670         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
671         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
672         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
673
674         /* Then we need a corrective translation, consisting of two parts:
675          *
676          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
677          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
678          *
679          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
680          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
681          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
682          *
683          * Combining these two translations gives these expressions.
684          */
685         
686         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
687         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
688         
689         _out_subtitle.image = _in_subtitle.image->scale (
690                 scaled_size,
691                 Scaler::from_id ("bicubic"),
692                 _in_subtitle.image->pixel_format (),
693                 true
694                 );
695
696         /* XXX: hack */
697         Time from = _in_subtitle.from;
698         Time to = _in_subtitle.to;
699         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
700         if (vc) {
701                 from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
702                 to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
703         }
704         
705         _out_subtitle.from = from + piece->content->position ();
706         _out_subtitle.to = to + piece->content->position ();
707 }
708
709 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
710  *  @return false if this could not be done.
711  */
712 bool
713 Player::repeat_last_video ()
714 {
715         if (!_last_incoming_video.image || !_have_valid_pieces) {
716                 return false;
717         }
718
719         process_video (
720                 _last_incoming_video.weak_piece,
721                 _last_incoming_video.image,
722                 _last_incoming_video.eyes,
723                 _last_incoming_video.same,
724                 _last_incoming_video.frame,
725                 _last_incoming_video.extra
726                 );
727
728         return true;
729 }
730
731 PlayerImage::PlayerImage (
732         shared_ptr<const Image> in,
733         Crop crop,
734         libdcp::Size inter_size,
735         libdcp::Size out_size,
736         Scaler const * scaler
737         )
738         : _in (in)
739         , _crop (crop)
740         , _inter_size (inter_size)
741         , _out_size (out_size)
742         , _scaler (scaler)
743 {
744
745 }
746
747 void
748 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
749 {
750         _subtitle_image = image;
751         _subtitle_position = pos;
752 }
753
754 shared_ptr<Image>
755 PlayerImage::image ()
756 {
757         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
758
759         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
760
761         if (_subtitle_image) {
762                 out->alpha_blend (_subtitle_image, _subtitle_position);
763         }
764
765         return out;
766 }