Rename SafeStringStream -> locked_stringstream. Bump deps for removal of stringstream.
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "player.h"
22 #include "film.h"
23 #include "audio_buffers.h"
24 #include "content_audio.h"
25 #include "dcp_content.h"
26 #include "job.h"
27 #include "image.h"
28 #include "raw_image_proxy.h"
29 #include "ratio.h"
30 #include "log.h"
31 #include "render_subtitles.h"
32 #include "config.h"
33 #include "content_video.h"
34 #include "player_video.h"
35 #include "frame_rate_change.h"
36 #include "audio_processor.h"
37 #include "playlist.h"
38 #include "referenced_reel_asset.h"
39 #include "decoder_factory.h"
40 #include "decoder.h"
41 #include "video_decoder.h"
42 #include "audio_decoder.h"
43 #include "subtitle_content.h"
44 #include "subtitle_decoder.h"
45 #include "ffmpeg_content.h"
46 #include "audio_content.h"
47 #include "content_subtitle.h"
48 #include "dcp_decoder.h"
49 #include "image_decoder.h"
50 #include <dcp/reel.h>
51 #include <dcp/reel_sound_asset.h>
52 #include <dcp/reel_subtitle_asset.h>
53 #include <dcp/reel_picture_asset.h>
54 #include <boost/foreach.hpp>
55 #include <stdint.h>
56 #include <algorithm>
57 #include <iostream>
58
59 #include "i18n.h"
60
61 #define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
62
63 using std::list;
64 using std::cout;
65 using std::min;
66 using std::max;
67 using std::min;
68 using std::vector;
69 using std::pair;
70 using std::map;
71 using std::make_pair;
72 using std::copy;
73 using boost::shared_ptr;
74 using boost::weak_ptr;
75 using boost::dynamic_pointer_cast;
76 using boost::optional;
77 using boost::scoped_ptr;
78
79 static bool
80 has_video (Content* c)
81 {
82         return static_cast<bool>(c->video);
83 }
84
85 static bool
86 has_audio (Content* c)
87 {
88         return static_cast<bool>(c->audio);
89 }
90
91 static bool
92 has_subtitle (Content* c)
93 {
94         return static_cast<bool>(c->subtitle);
95 }
96
97 Player::Player (shared_ptr<const Film> film, shared_ptr<const Playlist> playlist)
98         : _film (film)
99         , _playlist (playlist)
100         , _have_valid_pieces (false)
101         , _ignore_video (false)
102         , _ignore_audio (false)
103         , _always_burn_subtitles (false)
104         , _fast (false)
105         , _play_referenced (false)
106 {
107         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
108         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
109         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::playlist_content_changed, this, _1, _2, _3));
110         set_video_container_size (_film->frame_size ());
111
112         film_changed (Film::AUDIO_PROCESSOR);
113 }
114
115 void
116 Player::setup_pieces ()
117 {
118         _pieces.clear ();
119
120         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
121
122                 if (!i->paths_valid ()) {
123                         continue;
124                 }
125
126                 shared_ptr<Decoder> decoder = decoder_factory (i, _film->log());
127                 FrameRateChange frc (i->active_video_frame_rate(), _film->video_frame_rate());
128
129                 if (!decoder) {
130                         /* Not something that we can decode; e.g. Atmos content */
131                         continue;
132                 }
133
134                 if (decoder->video && _ignore_video) {
135                         decoder->video->set_ignore ();
136                 }
137
138                 if (decoder->audio && _ignore_audio) {
139                         decoder->audio->set_ignore ();
140                 }
141
142                 if (decoder->audio && _fast) {
143                         decoder->audio->set_fast ();
144                 }
145
146                 shared_ptr<DCPDecoder> dcp = dynamic_pointer_cast<DCPDecoder> (decoder);
147                 if (dcp && _play_referenced) {
148                         dcp->set_decode_referenced ();
149                 }
150
151                 _pieces.push_back (shared_ptr<Piece> (new Piece (i, decoder, frc)));
152         }
153
154         _have_valid_pieces = true;
155 }
156
157 void
158 Player::playlist_content_changed (weak_ptr<Content> w, int property, bool frequent)
159 {
160         shared_ptr<Content> c = w.lock ();
161         if (!c) {
162                 return;
163         }
164
165         if (
166                 property == ContentProperty::POSITION ||
167                 property == ContentProperty::LENGTH ||
168                 property == ContentProperty::TRIM_START ||
169                 property == ContentProperty::TRIM_END ||
170                 property == ContentProperty::PATH ||
171                 property == VideoContentProperty::FRAME_TYPE ||
172                 property == DCPContentProperty::CAN_BE_PLAYED ||
173                 property == SubtitleContentProperty::COLOUR ||
174                 property == SubtitleContentProperty::OUTLINE ||
175                 property == SubtitleContentProperty::SHADOW ||
176                 property == SubtitleContentProperty::EFFECT_COLOUR ||
177                 property == FFmpegContentProperty::SUBTITLE_STREAM ||
178                 property == VideoContentProperty::COLOUR_CONVERSION
179                 ) {
180
181                 _have_valid_pieces = false;
182                 Changed (frequent);
183
184         } else if (
185                 property == SubtitleContentProperty::LINE_SPACING ||
186                 property == SubtitleContentProperty::Y_SCALE
187                 ) {
188
189                 /* These changes just need the pieces' decoders to be reset.
190                    It's quite possible that other changes could be handled by
191                    this branch rather than the _have_valid_pieces = false branch
192                    above.  This would make things a lot faster.
193                 */
194
195                 reset_pieces ();
196                 Changed (frequent);
197
198         } else if (
199                 property == ContentProperty::VIDEO_FRAME_RATE ||
200                 property == SubtitleContentProperty::USE ||
201                 property == SubtitleContentProperty::X_OFFSET ||
202                 property == SubtitleContentProperty::Y_OFFSET ||
203                 property == SubtitleContentProperty::X_SCALE ||
204                 property == SubtitleContentProperty::FONTS ||
205                 property == VideoContentProperty::CROP ||
206                 property == VideoContentProperty::SCALE ||
207                 property == VideoContentProperty::FADE_IN ||
208                 property == VideoContentProperty::FADE_OUT
209                 ) {
210
211                 Changed (frequent);
212         }
213 }
214
215 void
216 Player::set_video_container_size (dcp::Size s)
217 {
218         _video_container_size = s;
219
220         _black_image.reset (new Image (AV_PIX_FMT_RGB24, _video_container_size, true));
221         _black_image->make_black ();
222 }
223
224 void
225 Player::playlist_changed ()
226 {
227         _have_valid_pieces = false;
228         Changed (false);
229 }
230
231 void
232 Player::film_changed (Film::Property p)
233 {
234         /* Here we should notice Film properties that affect our output, and
235            alert listeners that our output now would be different to how it was
236            last time we were run.
237         */
238
239         if (p == Film::CONTAINER) {
240                 Changed (false);
241         } else if (p == Film::VIDEO_FRAME_RATE) {
242                 /* Pieces contain a FrameRateChange which contains the DCP frame rate,
243                    so we need new pieces here.
244                 */
245                 _have_valid_pieces = false;
246                 Changed (false);
247         } else if (p == Film::AUDIO_PROCESSOR) {
248                 if (_film->audio_processor ()) {
249                         _audio_processor = _film->audio_processor()->clone (_film->audio_frame_rate ());
250                 }
251         }
252 }
253
254 list<PositionImage>
255 Player::transform_image_subtitles (list<ImageSubtitle> subs) const
256 {
257         list<PositionImage> all;
258
259         for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
260                 if (!i->image) {
261                         continue;
262                 }
263
264                 /* We will scale the subtitle up to fit _video_container_size */
265                 dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height);
266
267                 /* Then we need a corrective translation, consisting of two parts:
268                  *
269                  * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
270                  *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
271                  *
272                  * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
273                  *     (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and
274                  *     (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2).
275                  *
276                  * Combining these two translations gives these expressions.
277                  */
278
279                 all.push_back (
280                         PositionImage (
281                                 i->image->scale (
282                                         scaled_size,
283                                         dcp::YUV_TO_RGB_REC601,
284                                         i->image->pixel_format (),
285                                         true,
286                                         _fast
287                                         ),
288                                 Position<int> (
289                                         lrint (_video_container_size.width * i->rectangle.x),
290                                         lrint (_video_container_size.height * i->rectangle.y)
291                                         )
292                                 )
293                         );
294         }
295
296         return all;
297 }
298
299 shared_ptr<PlayerVideo>
300 Player::black_player_video_frame (DCPTime time) const
301 {
302         return shared_ptr<PlayerVideo> (
303                 new PlayerVideo (
304                         shared_ptr<const ImageProxy> (new RawImageProxy (_black_image)),
305                         time,
306                         Crop (),
307                         optional<double> (),
308                         _video_container_size,
309                         _video_container_size,
310                         EYES_BOTH,
311                         PART_WHOLE,
312                         PresetColourConversion::all().front().conversion
313                 )
314         );
315 }
316
317 /** @return All PlayerVideos at the given time.  There may be none if the content
318  *  at `time' is a DCP which we are passing through (i.e. referring to by reference)
319  *  or 2 if we have 3D.
320  */
321 list<shared_ptr<PlayerVideo> >
322 Player::get_video (DCPTime time, bool accurate)
323 {
324         if (!_have_valid_pieces) {
325                 setup_pieces ();
326         }
327
328         /* Find subtitles for possible burn-in */
329
330         PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false, true, accurate);
331
332         list<PositionImage> sub_images;
333
334         /* Image subtitles */
335         list<PositionImage> c = transform_image_subtitles (ps.image);
336         copy (c.begin(), c.end(), back_inserter (sub_images));
337
338         /* Text subtitles (rendered to an image) */
339         if (!ps.text.empty ()) {
340                 list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
341                 copy (s.begin (), s.end (), back_inserter (sub_images));
342         }
343
344         optional<PositionImage> subtitles;
345         if (!sub_images.empty ()) {
346                 subtitles = merge (sub_images);
347         }
348
349         /* Find pieces containing video which is happening now */
350
351         list<shared_ptr<Piece> > ov = overlaps (
352                 time,
353                 time + DCPTime::from_frames (1, _film->video_frame_rate ()),
354                 &has_video
355                 );
356
357         list<shared_ptr<PlayerVideo> > pvf;
358
359         if (ov.empty ()) {
360                 /* No video content at this time */
361                 pvf.push_back (black_player_video_frame (time));
362         } else {
363                 /* Some video content at this time */
364                 shared_ptr<Piece> last = *(ov.rbegin ());
365                 VideoFrameType const last_type = last->content->video->frame_type ();
366
367                 /* Get video from appropriate piece(s) */
368                 BOOST_FOREACH (shared_ptr<Piece> piece, ov) {
369
370                         shared_ptr<VideoDecoder> decoder = piece->decoder->video;
371                         DCPOMATIC_ASSERT (decoder);
372
373                         shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (piece->content);
374                         if (dcp_content && dcp_content->reference_video () && !_play_referenced) {
375                                 continue;
376                         }
377
378                         bool const use =
379                                 /* always use the last video */
380                                 piece == last ||
381                                 /* with a corresponding L/R eye if appropriate */
382                                 (last_type == VIDEO_FRAME_TYPE_3D_LEFT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_RIGHT) ||
383                                 (last_type == VIDEO_FRAME_TYPE_3D_RIGHT && piece->content->video->frame_type() == VIDEO_FRAME_TYPE_3D_LEFT);
384
385                         if (use) {
386                                 /* We want to use this piece */
387                                 list<ContentVideo> content_video = decoder->get (dcp_to_content_video (piece, time), accurate);
388                                 if (content_video.empty ()) {
389                                         pvf.push_back (black_player_video_frame (time));
390                                 } else {
391                                         dcp::Size image_size = piece->content->video->scale().size (
392                                                 piece->content->video, _video_container_size, _film->frame_size ()
393                                                 );
394
395                                         for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
396                                                 pvf.push_back (
397                                                         shared_ptr<PlayerVideo> (
398                                                                 new PlayerVideo (
399                                                                         i->image,
400                                                                         time,
401                                                                         piece->content->video->crop (),
402                                                                         piece->content->video->fade (i->frame.index()),
403                                                                         image_size,
404                                                                         _video_container_size,
405                                                                         i->frame.eyes(),
406                                                                         i->part,
407                                                                         piece->content->video->colour_conversion ()
408                                                                         )
409                                                                 )
410                                                         );
411                                         }
412                                 }
413                         } else {
414                                 /* Discard unused video */
415                                 decoder->get (dcp_to_content_video (piece, time), accurate);
416                         }
417                 }
418         }
419
420         if (subtitles) {
421                 BOOST_FOREACH (shared_ptr<PlayerVideo> p, pvf) {
422                         p->set_subtitle (subtitles.get ());
423                 }
424         }
425
426         return pvf;
427 }
428
429 /** @return Audio data or 0 if the only audio data here is referenced DCP data */
430 shared_ptr<AudioBuffers>
431 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
432 {
433         if (!_have_valid_pieces) {
434                 setup_pieces ();
435         }
436
437         Frame const length_frames = length.frames_round (_film->audio_frame_rate ());
438
439         shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
440         audio->make_silent ();
441
442         list<shared_ptr<Piece> > ov = overlaps (time, time + length, has_audio);
443         if (ov.empty ()) {
444                 return audio;
445         }
446
447         bool all_referenced = true;
448         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
449                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> (i->content);
450                 if (i->content->audio && (!dcp_content || !dcp_content->reference_audio ())) {
451                         /* There is audio content which is not from a DCP or not set to be referenced */
452                         all_referenced = false;
453                 }
454         }
455
456         if (all_referenced && !_play_referenced) {
457                 return shared_ptr<AudioBuffers> ();
458         }
459
460         BOOST_FOREACH (shared_ptr<Piece> i, ov) {
461
462                 DCPOMATIC_ASSERT (i->content->audio);
463                 shared_ptr<AudioDecoder> decoder = i->decoder->audio;
464                 DCPOMATIC_ASSERT (decoder);
465
466                 /* The time that we should request from the content */
467                 DCPTime request = time - DCPTime::from_seconds (i->content->audio->delay() / 1000.0);
468                 Frame request_frames = length_frames;
469                 DCPTime offset;
470                 if (request < DCPTime ()) {
471                         /* We went off the start of the content, so we will need to offset
472                            the stuff we get back.
473                         */
474                         offset = -request;
475                         request_frames += request.frames_round (_film->audio_frame_rate ());
476                         if (request_frames < 0) {
477                                 request_frames = 0;
478                         }
479                         request = DCPTime ();
480                 }
481
482                 Frame const content_frame = dcp_to_resampled_audio (i, request);
483
484                 BOOST_FOREACH (AudioStreamPtr j, i->content->audio->streams ()) {
485
486                         if (j->channels() == 0) {
487                                 /* Some content (e.g. DCPs) can have streams with no channels */
488                                 continue;
489                         }
490
491                         /* Audio from this piece's decoder stream (which might be more or less than what we asked for) */
492                         ContentAudio all = decoder->get (j, content_frame, request_frames, accurate);
493
494                         /* Gain */
495                         if (i->content->audio->gain() != 0) {
496                                 shared_ptr<AudioBuffers> gain (new AudioBuffers (all.audio));
497                                 gain->apply_gain (i->content->audio->gain ());
498                                 all.audio = gain;
499                         }
500
501                         /* Remap channels */
502                         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all.audio->frames()));
503                         dcp_mapped->make_silent ();
504                         AudioMapping map = j->mapping ();
505                         for (int i = 0; i < map.input_channels(); ++i) {
506                                 for (int j = 0; j < _film->audio_channels(); ++j) {
507                                         if (map.get (i, j) > 0) {
508                                                 dcp_mapped->accumulate_channel (
509                                                         all.audio.get(),
510                                                         i,
511                                                         j,
512                                                         map.get (i, j)
513                                                         );
514                                         }
515                                 }
516                         }
517
518                         if (_audio_processor) {
519                                 dcp_mapped = _audio_processor->run (dcp_mapped, _film->audio_channels ());
520                         }
521
522                         all.audio = dcp_mapped;
523
524                         audio->accumulate_frames (
525                                 all.audio.get(),
526                                 content_frame - all.frame,
527                                 offset.frames_round (_film->audio_frame_rate()),
528                                 min (Frame (all.audio->frames()), request_frames)
529                                 );
530                 }
531         }
532
533         return audio;
534 }
535
536 Frame
537 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
538 {
539         DCPTime s = t - piece->content->position ();
540         s = min (piece->content->length_after_trim(), s);
541         s = max (DCPTime(), s + DCPTime (piece->content->trim_start(), piece->frc));
542
543         /* It might seem more logical here to convert s to a ContentTime (using the FrameRateChange)
544            then convert that ContentTime to frames at the content's rate.  However this fails for
545            situations like content at 29.9978733fps, DCP at 30fps.  The accuracy of the Time type is not
546            enough to distinguish between the two with low values of time (e.g. 3200 in Time units).
547
548            Instead we convert the DCPTime using the DCP video rate then account for any skip/repeat.
549         */
550         return s.frames_floor (piece->frc.dcp) / piece->frc.factor ();
551 }
552
553 DCPTime
554 Player::content_video_to_dcp (shared_ptr<const Piece> piece, Frame f) const
555 {
556         /* See comment in dcp_to_content_video */
557         DCPTime const d = DCPTime::from_frames (f * piece->frc.factor(), piece->frc.dcp) - DCPTime (piece->content->trim_start (), piece->frc);
558         return max (DCPTime (), d + piece->content->position ());
559 }
560
561 Frame
562 Player::dcp_to_resampled_audio (shared_ptr<const Piece> piece, DCPTime t) const
563 {
564         DCPTime s = t - piece->content->position ();
565         s = min (piece->content->length_after_trim(), s);
566         /* See notes in dcp_to_content_video */
567         return max (DCPTime (), DCPTime (piece->content->trim_start (), piece->frc) + s).frames_floor (_film->audio_frame_rate ());
568 }
569
570 ContentTime
571 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
572 {
573         DCPTime s = t - piece->content->position ();
574         s = min (piece->content->length_after_trim(), s);
575         return max (ContentTime (), ContentTime (s, piece->frc) + piece->content->trim_start());
576 }
577
578 DCPTime
579 Player::content_subtitle_to_dcp (shared_ptr<const Piece> piece, ContentTime t) const
580 {
581         return max (DCPTime (), DCPTime (t - piece->content->trim_start(), piece->frc) + piece->content->position());
582 }
583
584 /** @param burnt true to return only subtitles to be burnt, false to return only
585  *  subtitles that should not be burnt.  This parameter will be ignored if
586  *  _always_burn_subtitles is true; in this case, all subtitles will be returned.
587  */
588 PlayerSubtitles
589 Player::get_subtitles (DCPTime time, DCPTime length, bool starting, bool burnt, bool accurate)
590 {
591         list<shared_ptr<Piece> > subs = overlaps (time, time + length, has_subtitle);
592
593         PlayerSubtitles ps (time, length);
594
595         for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
596                 if (!(*j)->content->subtitle->use () || (!_always_burn_subtitles && (burnt != (*j)->content->subtitle->burn ()))) {
597                         continue;
598                 }
599
600                 shared_ptr<DCPContent> dcp_content = dynamic_pointer_cast<DCPContent> ((*j)->content);
601                 if (dcp_content && dcp_content->reference_subtitle () && !_play_referenced) {
602                         continue;
603                 }
604
605                 shared_ptr<SubtitleDecoder> subtitle_decoder = (*j)->decoder->subtitle;
606                 ContentTime const from = dcp_to_content_subtitle (*j, time);
607                 /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */
608                 ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ());
609
610                 list<ContentImageSubtitle> image = subtitle_decoder->get_image (ContentTimePeriod (from, to), starting, accurate);
611                 for (list<ContentImageSubtitle>::iterator i = image.begin(); i != image.end(); ++i) {
612
613                         /* Apply content's subtitle offsets */
614                         i->sub.rectangle.x += (*j)->content->subtitle->x_offset ();
615                         i->sub.rectangle.y += (*j)->content->subtitle->y_offset ();
616
617                         /* Apply content's subtitle scale */
618                         i->sub.rectangle.width *= (*j)->content->subtitle->x_scale ();
619                         i->sub.rectangle.height *= (*j)->content->subtitle->y_scale ();
620
621                         /* Apply a corrective translation to keep the subtitle centred after that scale */
622                         i->sub.rectangle.x -= i->sub.rectangle.width * ((*j)->content->subtitle->x_scale() - 1);
623                         i->sub.rectangle.y -= i->sub.rectangle.height * ((*j)->content->subtitle->y_scale() - 1);
624
625                         ps.image.push_back (i->sub);
626                 }
627
628                 list<ContentTextSubtitle> text = subtitle_decoder->get_text (ContentTimePeriod (from, to), starting, accurate);
629                 BOOST_FOREACH (ContentTextSubtitle& ts, text) {
630                         BOOST_FOREACH (dcp::SubtitleString s, ts.subs) {
631                                 s.set_h_position (s.h_position() + (*j)->content->subtitle->x_offset ());
632                                 s.set_v_position (s.v_position() + (*j)->content->subtitle->y_offset ());
633                                 float const xs = (*j)->content->subtitle->x_scale();
634                                 float const ys = (*j)->content->subtitle->y_scale();
635                                 float size = s.size();
636
637                                 /* Adjust size to express the common part of the scaling;
638                                    e.g. if xs = ys = 0.5 we scale size by 2.
639                                 */
640                                 if (xs > 1e-5 && ys > 1e-5) {
641                                         size *= 1 / min (1 / xs, 1 / ys);
642                                 }
643                                 s.set_size (size);
644
645                                 /* Then express aspect ratio changes */
646                                 if (fabs (1.0 - xs / ys) > dcp::ASPECT_ADJUST_EPSILON) {
647                                         s.set_aspect_adjust (xs / ys);
648                                 }
649                                 s.set_in (dcp::Time(content_subtitle_to_dcp (*j, ts.period().from).seconds(), 1000));
650                                 s.set_out (dcp::Time(content_subtitle_to_dcp (*j, ts.period().to).seconds(), 1000));
651                                 ps.text.push_back (s);
652                                 ps.add_fonts ((*j)->content->subtitle->fonts ());
653                         }
654                 }
655         }
656
657         return ps;
658 }
659
660 list<shared_ptr<Font> >
661 Player::get_subtitle_fonts ()
662 {
663         if (!_have_valid_pieces) {
664                 setup_pieces ();
665         }
666
667         list<shared_ptr<Font> > fonts;
668         BOOST_FOREACH (shared_ptr<Piece>& p, _pieces) {
669                 if (p->content->subtitle) {
670                         /* XXX: things may go wrong if there are duplicate font IDs
671                            with different font files.
672                         */
673                         list<shared_ptr<Font> > f = p->content->subtitle->fonts ();
674                         copy (f.begin(), f.end(), back_inserter (fonts));
675                 }
676         }
677
678         return fonts;
679 }
680
681 /** Set this player never to produce any video data */
682 void
683 Player::set_ignore_video ()
684 {
685         _ignore_video = true;
686 }
687
688 /** Set this player never to produce any audio data */
689 void
690 Player::set_ignore_audio ()
691 {
692         _ignore_audio = true;
693 }
694
695 /** Set whether or not this player should always burn text subtitles into the image,
696  *  regardless of the content settings.
697  *  @param burn true to always burn subtitles, false to obey content settings.
698  */
699 void
700 Player::set_always_burn_subtitles (bool burn)
701 {
702         _always_burn_subtitles = burn;
703 }
704
705 void
706 Player::set_fast ()
707 {
708         _fast = true;
709         _have_valid_pieces = false;
710 }
711
712 void
713 Player::set_play_referenced ()
714 {
715         _play_referenced = true;
716         _have_valid_pieces = false;
717 }
718
719 list<ReferencedReelAsset>
720 Player::get_reel_assets ()
721 {
722         list<ReferencedReelAsset> a;
723
724         BOOST_FOREACH (shared_ptr<Content> i, _playlist->content ()) {
725                 shared_ptr<DCPContent> j = dynamic_pointer_cast<DCPContent> (i);
726                 if (!j) {
727                         continue;
728                 }
729
730                 scoped_ptr<DCPDecoder> decoder;
731                 try {
732                         decoder.reset (new DCPDecoder (j, _film->log()));
733                 } catch (...) {
734                         return a;
735                 }
736
737                 int64_t offset = 0;
738                 BOOST_FOREACH (shared_ptr<dcp::Reel> k, decoder->reels()) {
739                         DCPTime const from = i->position() + DCPTime::from_frames (offset, _film->video_frame_rate());
740                         if (j->reference_video ()) {
741                                 a.push_back (
742                                         ReferencedReelAsset (
743                                                 k->main_picture (),
744                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_picture()->duration(), _film->video_frame_rate()))
745                                                 )
746                                         );
747                         }
748
749                         if (j->reference_audio ()) {
750                                 a.push_back (
751                                         ReferencedReelAsset (
752                                                 k->main_sound (),
753                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_sound()->duration(), _film->video_frame_rate()))
754                                                 )
755                                         );
756                         }
757
758                         if (j->reference_subtitle ()) {
759                                 DCPOMATIC_ASSERT (k->main_subtitle ());
760                                 a.push_back (
761                                         ReferencedReelAsset (
762                                                 k->main_subtitle (),
763                                                 DCPTimePeriod (from, from + DCPTime::from_frames (k->main_subtitle()->duration(), _film->video_frame_rate()))
764                                                 )
765                                         );
766                         }
767
768                         /* Assume that main picture duration is the length of the reel */
769                         offset += k->main_picture()->duration ();
770                 }
771         }
772
773         return a;
774 }
775
776 list<shared_ptr<Piece> >
777 Player::overlaps (DCPTime from, DCPTime to, boost::function<bool (Content *)> valid)
778 {
779         if (!_have_valid_pieces) {
780                 setup_pieces ();
781         }
782
783         list<shared_ptr<Piece> > overlaps;
784         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
785                 if (valid (i->content.get ()) && i->content->position() < to && i->content->end() > from) {
786                         overlaps.push_back (i);
787                 }
788         }
789
790         return overlaps;
791 }
792
793 void
794 Player::reset_pieces ()
795 {
796         BOOST_FOREACH (shared_ptr<Piece> i, _pieces) {
797                 i->decoder->reset ();
798         }
799 }