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