Suspend scale / crop / window / subtitle overlay until we decide that a frame is...
[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                 _have_valid_pieces = true;
148         }
149
150         Time earliest_t = TIME_MAX;
151         shared_ptr<Piece> earliest;
152         enum {
153                 VIDEO,
154                 AUDIO
155         } type = VIDEO;
156
157         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
158                 if ((*i)->decoder->done ()) {
159                         continue;
160                 }
161
162                 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
163                 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
164
165                 if (_video && vd) {
166                         if ((*i)->video_position < earliest_t) {
167                                 earliest_t = (*i)->video_position;
168                                 earliest = *i;
169                                 type = VIDEO;
170                         }
171                 }
172
173                 if (_audio && ad && ad->has_audio ()) {
174                         if ((*i)->audio_position < earliest_t) {
175                                 earliest_t = (*i)->audio_position;
176                                 earliest = *i;
177                                 type = AUDIO;
178                         }
179                 }
180         }
181
182         if (!earliest) {
183                 flush ();
184                 return true;
185         }
186
187         switch (type) {
188         case VIDEO:
189                 if (earliest_t > _video_position) {
190                         emit_black ();
191                 } else {
192                         if (earliest->repeating ()) {
193                                 earliest->repeat (this);
194                         } else {
195                                 earliest->decoder->pass ();
196                         }
197                 }
198                 break;
199
200         case AUDIO:
201                 if (earliest_t > _audio_position) {
202                         emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
203                 } else {
204                         earliest->decoder->pass ();
205
206                         if (earliest->decoder->done()) {
207                                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
208                                 assert (ac);
209                                 shared_ptr<Resampler> re = resampler (ac, false);
210                                 if (re) {
211                                         shared_ptr<const AudioBuffers> b = re->flush ();
212                                         if (b->frames ()) {
213                                                 process_audio (earliest, b, ac->audio_length ());
214                                         }
215                                 }
216                         }
217                 }
218                 break;
219         }
220
221         if (_audio) {
222                 boost::optional<Time> audio_done_up_to;
223                 for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
224                         if ((*i)->decoder->done ()) {
225                                 continue;
226                         }
227
228                         if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
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 #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         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
349         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
350                 if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
351                         dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
352                 }
353         }
354
355         audio = dcp_mapped;
356
357         /* We must cut off anything that comes before the start of all time */
358         if (time < 0) {
359                 int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
360                 if (frames >= audio->frames ()) {
361                         return;
362                 }
363
364                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
365                 trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
366
367                 audio = trimmed;
368                 time = 0;
369         }
370
371         _audio_merger.push (audio, time);
372         piece->audio_position += _film->audio_frames_to_time (audio->frames ());
373 }
374
375 void
376 Player::flush ()
377 {
378         TimedAudioBuffers<Time> tb = _audio_merger.flush ();
379         if (tb.audio) {
380                 Audio (tb.audio, tb.time);
381                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
382         }
383
384         while (_video_position < _audio_position) {
385                 emit_black ();
386         }
387
388         while (_audio_position < _video_position) {
389                 emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
390         }
391         
392 }
393
394 /** Seek so that the next pass() will yield (approximately) the requested frame.
395  *  Pass accurate = true to try harder to get close to the request.
396  *  @return true on error
397  */
398 void
399 Player::seek (Time t, bool accurate)
400 {
401         if (!_have_valid_pieces) {
402                 setup_pieces ();
403                 _have_valid_pieces = true;
404         }
405
406         if (_pieces.empty ()) {
407                 return;
408         }
409
410         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
411                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
412                 if (!vc) {
413                         continue;
414                 }
415
416                 /* s is the offset of t from the start position of this content */
417                 Time s = t - vc->position ();
418                 s = max (static_cast<Time> (0), s);
419                 s = min (vc->length_after_trim(), s);
420
421                 /* Hence set the piece positions to the `global' time */
422                 (*i)->video_position = (*i)->audio_position = vc->position() + s;
423
424                 /* And seek the decoder */
425                 dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
426                         vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
427                         );
428
429                 (*i)->reset_repeat ();
430         }
431
432         _video_position = _audio_position = t;
433
434         /* XXX: don't seek audio because we don't need to... */
435 }
436
437 void
438 Player::setup_pieces ()
439 {
440         list<shared_ptr<Piece> > old_pieces = _pieces;
441
442         _pieces.clear ();
443
444         ContentList content = _playlist->content ();
445         sort (content.begin(), content.end(), ContentSorter ());
446
447         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
448
449                 shared_ptr<Piece> piece (new Piece (*i));
450
451                 /* XXX: into content? */
452
453                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
454                 if (fc) {
455                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
456                         
457                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
458                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
459                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
460
461                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
462                         piece->decoder = fd;
463                 }
464                 
465                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
466                 if (ic) {
467                         bool reusing = false;
468                         
469                         /* See if we can re-use an old ImageDecoder */
470                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
471                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
472                                 if (imd && imd->content() == ic) {
473                                         piece = *j;
474                                         reusing = true;
475                                 }
476                         }
477
478                         if (!reusing) {
479                                 shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
480                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
481                                 piece->decoder = id;
482                         }
483                 }
484
485                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
486                 if (sc) {
487                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
488                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
489
490                         piece->decoder = sd;
491                 }
492
493                 _pieces.push_back (piece);
494         }
495 }
496
497 void
498 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
499 {
500         shared_ptr<Content> c = w.lock ();
501         if (!c) {
502                 return;
503         }
504
505         if (
506                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
507                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END
508                 ) {
509                 
510                 _have_valid_pieces = false;
511                 Changed (frequent);
512
513         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
514
515                 update_subtitle ();
516                 Changed (frequent);
517
518         } else if (
519                 property == VideoContentProperty::VIDEO_FRAME_TYPE || property == VideoContentProperty::VIDEO_CROP ||
520                 property == VideoContentProperty::VIDEO_RATIO
521                 ) {
522                 
523                 Changed (frequent);
524
525         } else if (property == ContentProperty::PATH) {
526
527                 Changed (frequent);
528         }
529 }
530
531 void
532 Player::playlist_changed ()
533 {
534         _have_valid_pieces = false;
535         Changed (false);
536 }
537
538 void
539 Player::set_video_container_size (libdcp::Size s)
540 {
541         _video_container_size = s;
542
543         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
544         im->make_black ();
545         
546         _black_frame.reset (
547                 new PlayerImage (
548                         im,
549                         Crop(),
550                         _video_container_size,
551                         _video_container_size,
552                         Scaler::from_id ("bicubic")
553                         )
554                 );
555 }
556
557 shared_ptr<Resampler>
558 Player::resampler (shared_ptr<AudioContent> c, bool create)
559 {
560         map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
561         if (i != _resamplers.end ()) {
562                 return i->second;
563         }
564
565         if (!create) {
566                 return shared_ptr<Resampler> ();
567         }
568
569         _film->log()->log (
570                 String::compose (
571                         "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
572                         )
573                 );
574         
575         shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
576         _resamplers[c] = r;
577         return r;
578 }
579
580 void
581 Player::emit_black ()
582 {
583 #ifdef DCPOMATIC_DEBUG
584         _last_video.reset ();
585 #endif
586
587         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
588         _video_position += _film->video_frames_to_time (1);
589         _last_emit_was_black = true;
590 }
591
592 void
593 Player::emit_silence (OutputAudioFrame most)
594 {
595         if (most == 0) {
596                 return;
597         }
598         
599         OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
600         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
601         silence->make_silent ();
602         Audio (silence, _audio_position);
603         _audio_position += _film->audio_frames_to_time (N);
604 }
605
606 void
607 Player::film_changed (Film::Property p)
608 {
609         /* Here we should notice Film properties that affect our output, and
610            alert listeners that our output now would be different to how it was
611            last time we were run.
612         */
613
614         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
615                 Changed (false);
616         }
617 }
618
619 void
620 Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
621 {
622         _in_subtitle.piece = weak_piece;
623         _in_subtitle.image = image;
624         _in_subtitle.rect = rect;
625         _in_subtitle.from = from;
626         _in_subtitle.to = to;
627
628         update_subtitle ();
629 }
630
631 void
632 Player::update_subtitle ()
633 {
634         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
635         if (!piece) {
636                 return;
637         }
638
639         if (!_in_subtitle.image) {
640                 _out_subtitle.image.reset ();
641                 return;
642         }
643
644         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
645         assert (sc);
646
647         dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
648         libdcp::Size scaled_size;
649
650         in_rect.y += sc->subtitle_offset ();
651
652         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
653         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
654         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
655
656         /* Then we need a corrective translation, consisting of two parts:
657          *
658          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
659          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
660          *
661          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
662          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
663          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
664          *
665          * Combining these two translations gives these expressions.
666          */
667         
668         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
669         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
670         
671         _out_subtitle.image = _in_subtitle.image->scale (
672                 scaled_size,
673                 Scaler::from_id ("bicubic"),
674                 _in_subtitle.image->pixel_format (),
675                 true
676                 );
677         _out_subtitle.from = _in_subtitle.from + piece->content->position ();
678         _out_subtitle.to = _in_subtitle.to + piece->content->position ();
679 }
680
681 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
682  *  @return false if this could not be done.
683  */
684 bool
685 Player::repeat_last_video ()
686 {
687         if (!_last_incoming_video.image) {
688                 return false;
689         }
690
691         process_video (
692                 _last_incoming_video.weak_piece,
693                 _last_incoming_video.image,
694                 _last_incoming_video.eyes,
695                 _last_incoming_video.same,
696                 _last_incoming_video.frame,
697                 _last_incoming_video.extra
698                 );
699
700         return true;
701 }
702
703 PlayerImage::PlayerImage (
704         shared_ptr<const Image> in,
705         Crop crop,
706         libdcp::Size inter_size,
707         libdcp::Size out_size,
708         Scaler const * scaler
709         )
710         : _in (in)
711         , _crop (crop)
712         , _inter_size (inter_size)
713         , _out_size (out_size)
714         , _scaler (scaler)
715 {
716
717 }
718
719 void
720 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
721 {
722         _subtitle_image = image;
723         _subtitle_position = pos;
724 }
725
726 shared_ptr<Image>
727 PlayerImage::image ()
728 {
729         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
730
731         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
732
733         if (_subtitle_image) {
734                 out->alpha_blend (_subtitle_image, _subtitle_position);
735         }
736
737         return out;
738 }