Small cleanup with _have_valid_pieces; don't allow repeat_last_video if we don't...
[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 #ifdef DCPOMATIC_DEBUG
297         _last_video = piece->content;
298 #endif
299
300         Video (pi, eyes, content->colour_conversion(), same, time);
301
302         _last_emit_was_black = false;
303         _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
304
305         if (frc.repeat > 1 && !piece->repeating ()) {
306                 piece->set_repeat (_last_incoming_video, frc.repeat - 1);
307         }
308 }
309
310 void
311 Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
312 {
313         shared_ptr<Piece> piece = weak_piece.lock ();
314         if (!piece) {
315                 return;
316         }
317
318         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
319         assert (content);
320
321         /* Gain */
322         if (content->audio_gain() != 0) {
323                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
324                 gain->apply_gain (content->audio_gain ());
325                 audio = gain;
326         }
327
328         /* Resample */
329         if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
330                 shared_ptr<Resampler> r = resampler (content, true);
331                 pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
332                 audio = ro.first;
333                 frame = ro.second;
334         }
335         
336         Time const relative_time = _film->audio_frames_to_time (frame);
337
338         if (content->trimmed (relative_time)) {
339                 return;
340         }
341
342         Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
343         
344         /* Remap channels */
345         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
346         dcp_mapped->make_silent ();
347         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
348         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
349                 if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
350                         dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
351                 }
352         }
353
354         audio = dcp_mapped;
355
356         /* We must cut off anything that comes before the start of all time */
357         if (time < 0) {
358                 int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
359                 if (frames >= audio->frames ()) {
360                         return;
361                 }
362
363                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
364                 trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
365
366                 audio = trimmed;
367                 time = 0;
368         }
369
370         _audio_merger.push (audio, time);
371         piece->audio_position += _film->audio_frames_to_time (audio->frames ());
372 }
373
374 void
375 Player::flush ()
376 {
377         TimedAudioBuffers<Time> tb = _audio_merger.flush ();
378         if (tb.audio) {
379                 Audio (tb.audio, tb.time);
380                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
381         }
382
383         while (_video_position < _audio_position) {
384                 emit_black ();
385         }
386
387         while (_audio_position < _video_position) {
388                 emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
389         }
390         
391 }
392
393 /** Seek so that the next pass() will yield (approximately) the requested frame.
394  *  Pass accurate = true to try harder to get close to the request.
395  *  @return true on error
396  */
397 void
398 Player::seek (Time t, bool accurate)
399 {
400         if (!_have_valid_pieces) {
401                 setup_pieces ();
402         }
403
404         if (_pieces.empty ()) {
405                 return;
406         }
407
408         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
409                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
410                 if (!vc) {
411                         continue;
412                 }
413
414                 /* s is the offset of t from the start position of this content */
415                 Time s = t - vc->position ();
416                 s = max (static_cast<Time> (0), s);
417                 s = min (vc->length_after_trim(), s);
418
419                 /* Hence set the piece positions to the `global' time */
420                 (*i)->video_position = (*i)->audio_position = vc->position() + s;
421
422                 /* And seek the decoder */
423                 dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
424                         vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
425                         );
426
427                 (*i)->reset_repeat ();
428         }
429
430         _video_position = _audio_position = t;
431
432         /* XXX: don't seek audio because we don't need to... */
433 }
434
435 void
436 Player::setup_pieces ()
437 {
438         list<shared_ptr<Piece> > old_pieces = _pieces;
439
440         _pieces.clear ();
441
442         ContentList content = _playlist->content ();
443         sort (content.begin(), content.end(), ContentSorter ());
444
445         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
446
447                 shared_ptr<Piece> piece (new Piece (*i));
448
449                 /* XXX: into content? */
450
451                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
452                 if (fc) {
453                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
454                         
455                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
456                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
457                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
458
459                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
460                         piece->decoder = fd;
461                 }
462                 
463                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
464                 if (ic) {
465                         bool reusing = false;
466                         
467                         /* See if we can re-use an old ImageDecoder */
468                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
469                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
470                                 if (imd && imd->content() == ic) {
471                                         piece = *j;
472                                         reusing = true;
473                                 }
474                         }
475
476                         if (!reusing) {
477                                 shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
478                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
479                                 piece->decoder = id;
480                         }
481                 }
482
483                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
484                 if (sc) {
485                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
486                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
487
488                         piece->decoder = sd;
489                 }
490
491                 _pieces.push_back (piece);
492         }
493
494         _have_valid_pieces = true;
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 || !_have_valid_pieces) {
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 }