263998693e7887188eaf1f4d3b87f3cefa935ab6
[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 <algorithm>
22 #include "player.h"
23 #include "film.h"
24 #include "ffmpeg_decoder.h"
25 #include "ffmpeg_content.h"
26 #include "image_decoder.h"
27 #include "image_content.h"
28 #include "sndfile_decoder.h"
29 #include "sndfile_content.h"
30 #include "subtitle_content.h"
31 #include "playlist.h"
32 #include "job.h"
33 #include "image.h"
34 #include "ratio.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 using boost::optional;
49
50 class Piece
51 {
52 public:
53         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
54                 : content (c)
55                 , decoder (d)
56                 , frc (f)
57         {}
58
59         shared_ptr<Content> content;
60         shared_ptr<Decoder> decoder;
61         FrameRateChange frc;
62 };
63
64 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
65         : _film (f)
66         , _playlist (p)
67         , _video (true)
68         , _audio (true)
69         , _have_valid_pieces (false)
70         , _video_position (0)
71         , _audio_position (0)
72         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
73         , _last_emit_was_black (false)
74         , _just_did_inaccurate_seek (false)
75         , _approximate_size (false)
76 {
77         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
78         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
79         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
80         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
81 }
82
83 void
84 Player::disable_video ()
85 {
86         _video = false;
87 }
88
89 void
90 Player::disable_audio ()
91 {
92         _audio = false;
93 }
94
95 bool
96 Player::pass ()
97 {
98         if (!_have_valid_pieces) {
99                 setup_pieces ();
100         }
101
102         /* Interrogate all our pieces to find the one with the earliest decoded data */
103
104         shared_ptr<Piece> earliest_piece;
105         shared_ptr<Decoded> earliest_decoded;
106         DCPTime earliest_time = TIME_MAX;
107         DCPTime earliest_audio = TIME_MAX;
108
109         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
110
111                 DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
112                 
113                 bool done = false;
114                 shared_ptr<Decoded> dec;
115                 while (!done) {
116                         dec = (*i)->decoder->peek ();
117                         if (!dec) {
118                                 /* Decoder has nothing else to give us */
119                                 break;
120                         }
121
122                         dec->set_dcp_times ((*i)->frc.speed_up, offset);
123                         DCPTime const t = dec->dcp_time - offset;
124                         if (t >= (*i)->content->full_length() - (*i)->content->trim_end ()) {
125                                 /* In the end-trimmed part; decoder has nothing eles to give us */
126                                 dec.reset ();
127                                 done = true;
128                         } else if (t >= (*i)->content->trim_start ()) {
129                                 /* Within the un-trimmed part; everything's ok */
130                                 done = true;
131                         } else {
132                                 /* Within the start-trimmed part; get something else */
133                                 (*i)->decoder->consume ();
134                         }
135                 }
136
137                 if (!dec) {
138                         continue;
139                 }
140
141                 if (dec->dcp_time < earliest_time) {
142                         earliest_piece = *i;
143                         earliest_decoded = dec;
144                         earliest_time = dec->dcp_time;
145                 }
146
147                 if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
148                         earliest_audio = dec->dcp_time;
149                 }
150         }
151                 
152         if (!earliest_piece) {
153                 flush ();
154                 return true;
155         }
156
157         if (earliest_audio != TIME_MAX) {
158                 TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
159                 Audio (tb.audio, tb.time);
160                 /* This assumes that the audio_frames_to_time conversion is exact
161                    so that there are no accumulated errors caused by rounding.
162                 */
163                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
164         }
165
166         /* Emit the earliest thing */
167
168         shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
169         shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
170         shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
171
172         /* Will be set to false if we shouldn't consume the peeked DecodedThing */
173         bool consume = true;
174
175         /* This is the margin either side of _{video,audio}_position that we will accept
176            as a starting point for a frame consecutive to the previous.
177         */
178         DCPTime const margin = TIME_HZ / (2 * _film->video_frame_rate ());
179         
180         if (dv && _video) {
181
182                 if (_just_did_inaccurate_seek) {
183
184                         /* Just emit; no subtlety */
185                         emit_video (earliest_piece, dv);
186                         step_video_position (dv);
187                         
188                 } else if (dv->dcp_time - _video_position > margin) {
189
190                         /* Too far ahead */
191
192                         list<shared_ptr<Piece> >::iterator i = _pieces.begin();
193                         while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
194                                 ++i;
195                         }
196
197                         if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
198                                 /* We're outside all video content */
199                                 emit_black ();
200                         } else {
201                                 /* We're inside some video; repeat the frame */
202                                 _last_incoming_video.video->dcp_time = _video_position;
203                                 emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
204                                 step_video_position (_last_incoming_video.video);
205                         }
206
207                         consume = false;
208
209                 } else if (abs (dv->dcp_time - _video_position) < margin) {
210                         /* We're ok */
211                         emit_video (earliest_piece, dv);
212                         step_video_position (dv);
213                 } else {
214                         /* Too far behind: skip */
215                 }
216
217                 _just_did_inaccurate_seek = false;
218
219         } else if (da && _audio) {
220
221                 if (da->dcp_time - _audio_position > margin) {
222                         /* Too far ahead */
223                         emit_silence (da->dcp_time - _audio_position);
224                         consume = false;
225                 } else if (abs (da->dcp_time - _audio_position) < margin) {
226                         /* We're ok */
227                         emit_audio (earliest_piece, da);
228                 } else {
229                         /* Too far behind: skip */
230                 }
231                 
232         } else if (ds && _video) {
233                 _in_subtitle.piece = earliest_piece;
234                 _in_subtitle.subtitle = ds;
235                 update_subtitle ();
236         }
237
238         if (consume) {
239                 earliest_piece->decoder->consume ();
240         }                       
241         
242         return false;
243 }
244
245 void
246 Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
247 {
248         /* Keep a note of what came in so that we can repeat it if required */
249         _last_incoming_video.weak_piece = weak_piece;
250         _last_incoming_video.video = video;
251         
252         shared_ptr<Piece> piece = weak_piece.lock ();
253         if (!piece) {
254                 return;
255         }
256
257         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
258         assert (content);
259
260         FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
261
262         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
263         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
264         if (_approximate_size) {
265                 image_size.width &= ~3;
266                 image_size.height &= ~3;
267         }
268
269         shared_ptr<PlayerImage> pi (
270                 new PlayerImage (
271                         video->image,
272                         content->crop(),
273                         image_size,
274                         _video_container_size,
275                         _film->scaler()
276                         )
277                 );
278         
279         if (
280                 _film->with_subtitles () &&
281                 _out_subtitle.image &&
282                 video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
283                 ) {
284
285                 Position<int> const container_offset (
286                         (_video_container_size.width - image_size.width) / 2,
287                         (_video_container_size.height - image_size.height) / 2
288                         );
289
290                 pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
291         }
292                                             
293 #ifdef DCPOMATIC_DEBUG
294         _last_video = piece->content;
295 #endif
296
297         Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
298         
299         _last_emit_was_black = false;
300 }
301
302 void
303 Player::step_video_position (shared_ptr<DecodedVideo> video)
304 {
305         /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
306         if (video->eyes != EYES_LEFT) {
307                 /* This assumes that the video_frames_to_time conversion is exact
308                    so that there are no accumulated errors caused by rounding.
309                 */
310                 _video_position += _film->video_frames_to_time (1);
311         }
312 }
313
314 void
315 Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
316 {
317         shared_ptr<Piece> piece = weak_piece.lock ();
318         if (!piece) {
319                 return;
320         }
321
322         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
323         assert (content);
324
325         /* Gain */
326         if (content->audio_gain() != 0) {
327                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
328                 gain->apply_gain (content->audio_gain ());
329                 audio->data = gain;
330         }
331
332         /* Remap channels */
333         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
334         dcp_mapped->make_silent ();
335         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
336         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
337                 if (i->first < audio->data->channels() && i->second < dcp_mapped->channels()) {
338                         dcp_mapped->accumulate_channel (audio->data.get(), i->first, i->second);
339                 }
340         }
341
342         audio->data = dcp_mapped;
343
344         /* Delay */
345         audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
346         if (audio->dcp_time < 0) {
347                 int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
348                 if (frames >= audio->data->frames ()) {
349                         return;
350                 }
351
352                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
353                 trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
354
355                 audio->data = trimmed;
356                 audio->dcp_time = 0;
357         }
358
359         _audio_merger.push (audio->data, audio->dcp_time);
360 }
361
362 void
363 Player::flush ()
364 {
365         TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
366         if (_audio && tb.audio) {
367                 Audio (tb.audio, tb.time);
368                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
369         }
370
371         while (_video && _video_position < _audio_position) {
372                 emit_black ();
373         }
374
375         while (_audio && _audio_position < _video_position) {
376                 emit_silence (_video_position - _audio_position);
377         }
378         
379 }
380
381 /** Seek so that the next pass() will yield (approximately) the requested frame.
382  *  Pass accurate = true to try harder to get close to the request.
383  *  @return true on error
384  */
385 void
386 Player::seek (DCPTime t, bool accurate)
387 {
388         if (!_have_valid_pieces) {
389                 setup_pieces ();
390         }
391
392         if (_pieces.empty ()) {
393                 return;
394         }
395
396         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
397                 /* s is the offset of t from the start position of this content */
398                 DCPTime s = t - (*i)->content->position ();
399                 s = max (static_cast<DCPTime> (0), s);
400                 s = min ((*i)->content->length_after_trim(), s);
401
402                 /* Convert this to the content time */
403                 ContentTime ct = (s + (*i)->content->trim_start()) * (*i)->frc.speed_up;
404
405                 /* And seek the decoder */
406                 (*i)->decoder->seek (ct, accurate);
407         }
408
409         _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
410         _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
411
412         _audio_merger.clear (_audio_position);
413
414         if (!accurate) {
415                 /* We just did an inaccurate seek, so it's likely that the next thing seen
416                    out of pass() will be a fair distance from _{video,audio}_position.  Setting
417                    this flag stops pass() from trying to fix that: we assume that if it
418                    was an inaccurate seek then the caller does not care too much about
419                    inserting black/silence to keep the time tidy.
420                 */
421                 _just_did_inaccurate_seek = true;
422         }
423 }
424
425 void
426 Player::setup_pieces ()
427 {
428         list<shared_ptr<Piece> > old_pieces = _pieces;
429         _pieces.clear ();
430
431         ContentList content = _playlist->content ();
432
433         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
434
435                 shared_ptr<Decoder> decoder;
436                 optional<FrameRateChange> frc;
437
438                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
439                 if (fc) {
440                         decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
441                         frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
442                 }
443                 
444                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
445                 if (ic) {
446                         /* See if we can re-use an old ImageDecoder */
447                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
448                                 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
449                                 if (imd && imd->content() == ic) {
450                                         decoder = imd;
451                                 }
452                         }
453
454                         if (!decoder) {
455                                 decoder.reset (new ImageDecoder (_film, ic));
456                         }
457
458                         frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
459                 }
460
461                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
462                 if (sc) {
463                         decoder.reset (new SndfileDecoder (_film, sc));
464
465                         /* Working out the frc for this content is a bit tricky: what if it overlaps
466                            two pieces of video content with different frame rates?  For now, use
467                            the one with the best overlap.
468                         */
469
470                         DCPTime best_overlap_t = 0;
471                         shared_ptr<VideoContent> best_overlap;
472                         for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
473                                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
474                                 if (!vc) {
475                                         continue;
476                                 }
477
478                                 DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
479                                 if (overlap > best_overlap_t) {
480                                         best_overlap = vc;
481                                         best_overlap_t = overlap;
482                                 }
483                         }
484
485                         if (best_overlap) {
486                                 frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
487                         } else {
488                                 /* No video overlap; e.g. if the DCP is just audio */
489                                 frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
490                         }
491                 }
492
493                 ContentTime st = (*i)->trim_start() * frc->speed_up;
494                 decoder->seek (st, true);
495                 
496                 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
497         }
498
499         _have_valid_pieces = true;
500
501         /* The Piece for the _last_incoming_video will no longer be valid */
502         _last_incoming_video.video.reset ();
503
504         _video_position = _audio_position = 0;
505 }
506
507 void
508 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
509 {
510         shared_ptr<Content> c = w.lock ();
511         if (!c) {
512                 return;
513         }
514
515         if (
516                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
517                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
518                 property == VideoContentProperty::VIDEO_FRAME_TYPE 
519                 ) {
520                 
521                 _have_valid_pieces = false;
522                 Changed (frequent);
523
524         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
525
526                 update_subtitle ();
527                 Changed (frequent);
528
529         } else if (property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO) {
530                 
531                 Changed (frequent);
532
533         } else if (property == ContentProperty::PATH) {
534
535                 Changed (frequent);
536         }
537 }
538
539 void
540 Player::playlist_changed ()
541 {
542         _have_valid_pieces = false;
543         Changed (false);
544 }
545
546 void
547 Player::set_video_container_size (libdcp::Size s)
548 {
549         _video_container_size = s;
550
551         shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
552         im->make_black ();
553         
554         _black_frame.reset (
555                 new PlayerImage (
556                         im,
557                         Crop(),
558                         _video_container_size,
559                         _video_container_size,
560                         Scaler::from_id ("bicubic")
561                         )
562                 );
563 }
564
565 void
566 Player::emit_black ()
567 {
568 #ifdef DCPOMATIC_DEBUG
569         _last_video.reset ();
570 #endif
571
572         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
573         _video_position += _film->video_frames_to_time (1);
574         _last_emit_was_black = true;
575 }
576
577 void
578 Player::emit_silence (DCPTime most)
579 {
580         if (most == 0) {
581                 return;
582         }
583         
584         DCPTime t = min (most, TIME_HZ / 2);
585         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
586         silence->make_silent ();
587         Audio (silence, _audio_position);
588         
589         _audio_position += t;
590 }
591
592 void
593 Player::film_changed (Film::Property p)
594 {
595         /* Here we should notice Film properties that affect our output, and
596            alert listeners that our output now would be different to how it was
597            last time we were run.
598         */
599
600         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
601                 Changed (false);
602         }
603 }
604
605 void
606 Player::update_subtitle ()
607 {
608         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
609         if (!piece) {
610                 return;
611         }
612
613         if (!_in_subtitle.subtitle->image) {
614                 _out_subtitle.image.reset ();
615                 return;
616         }
617
618         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
619         assert (sc);
620
621         dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
622         libdcp::Size scaled_size;
623
624         in_rect.y += sc->subtitle_offset ();
625
626         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
627         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
628         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
629
630         /* Then we need a corrective translation, consisting of two parts:
631          *
632          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
633          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
634          *
635          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
636          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
637          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
638          *
639          * Combining these two translations gives these expressions.
640          */
641         
642         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
643         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
644
645         _out_subtitle.image = _in_subtitle.subtitle->image->scale (
646                 scaled_size,
647                 Scaler::from_id ("bicubic"),
648                 PIX_FMT_RGBA,
649                 true
650                 );
651
652         _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
653         _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
654 }
655
656 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
657  *  @return false if this could not be done.
658  */
659 bool
660 Player::repeat_last_video ()
661 {
662         if (!_last_incoming_video.video || !_have_valid_pieces) {
663                 return false;
664         }
665
666         emit_video (
667                 _last_incoming_video.weak_piece,
668                 _last_incoming_video.video
669                 );
670
671         return true;
672 }
673
674 void
675 Player::set_approximate_size ()
676 {
677         _approximate_size = true;
678 }
679                               
680
681 PlayerImage::PlayerImage (
682         shared_ptr<const Image> in,
683         Crop crop,
684         libdcp::Size inter_size,
685         libdcp::Size out_size,
686         Scaler const * scaler
687         )
688         : _in (in)
689         , _crop (crop)
690         , _inter_size (inter_size)
691         , _out_size (out_size)
692         , _scaler (scaler)
693 {
694
695 }
696
697 void
698 PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
699 {
700         _subtitle_image = image;
701         _subtitle_position = pos;
702 }
703
704 shared_ptr<Image>
705 PlayerImage::image (AVPixelFormat format, bool aligned)
706 {
707         shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
708         
709         Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
710
711         if (_subtitle_image) {
712                 out->alpha_blend (_subtitle_image, _subtitle_position);
713         }
714
715         return out;
716 }
717