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