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