2 Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
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.
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.
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.
24 #include "ffmpeg_decoder.h"
25 #include "audio_buffers.h"
26 #include "ffmpeg_content.h"
27 #include "image_decoder.h"
28 #include "image_content.h"
29 #include "sndfile_decoder.h"
30 #include "sndfile_content.h"
31 #include "subtitle_content.h"
32 #include "subrip_decoder.h"
33 #include "subrip_content.h"
40 #include "render_subtitles.h"
41 #include "dcp_video.h"
43 #include "content_video.h"
54 using boost::shared_ptr;
55 using boost::weak_ptr;
56 using boost::dynamic_pointer_cast;
57 using boost::optional;
59 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
62 , _have_valid_pieces (false)
63 , _approximate_size (false)
64 , _burn_subtitles (false)
66 _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
67 _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
68 _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
69 set_video_container_size (_film->frame_size ());
73 Player::setup_pieces ()
75 list<shared_ptr<Piece> > old_pieces = _pieces;
78 ContentList content = _playlist->content ();
80 for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
82 if (!(*i)->paths_valid ()) {
86 shared_ptr<Decoder> decoder;
87 optional<FrameRateChange> frc;
89 /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
90 DCPTime best_overlap_t;
91 shared_ptr<VideoContent> best_overlap;
92 for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
93 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
98 DCPTime const overlap = max (vc->position(), (*i)->position()) - min (vc->end(), (*i)->end());
99 if (overlap > best_overlap_t) {
101 best_overlap_t = overlap;
105 optional<FrameRateChange> best_overlap_frc;
107 best_overlap_frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
109 /* No video overlap; e.g. if the DCP is just audio */
110 best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
114 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
116 decoder.reset (new FFmpegDecoder (fc, _film->log()));
117 frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
121 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
123 /* See if we can re-use an old ImageDecoder */
124 for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
125 shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
126 if (imd && imd->content() == ic) {
132 decoder.reset (new ImageDecoder (ic));
135 frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
139 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
141 decoder.reset (new SndfileDecoder (sc));
142 frc = best_overlap_frc;
146 shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
148 decoder.reset (new SubRipDecoder (rc));
149 frc = best_overlap_frc;
152 _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
155 _have_valid_pieces = true;
159 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
161 shared_ptr<Content> c = w.lock ();
167 property == ContentProperty::POSITION ||
168 property == ContentProperty::LENGTH ||
169 property == ContentProperty::TRIM_START ||
170 property == ContentProperty::TRIM_END ||
171 property == ContentProperty::PATH ||
172 property == VideoContentProperty::VIDEO_FRAME_TYPE
175 _have_valid_pieces = false;
179 property == SubtitleContentProperty::SUBTITLE_X_OFFSET ||
180 property == SubtitleContentProperty::SUBTITLE_Y_OFFSET ||
181 property == SubtitleContentProperty::SUBTITLE_SCALE ||
182 property == VideoContentProperty::VIDEO_CROP ||
183 property == VideoContentProperty::VIDEO_SCALE ||
184 property == VideoContentProperty::VIDEO_FRAME_RATE
192 Player::playlist_changed ()
194 _have_valid_pieces = false;
199 Player::set_video_container_size (dcp::Size s)
201 _video_container_size = s;
203 _black_image.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
204 _black_image->make_black ();
208 Player::film_changed (Film::Property p)
210 /* Here we should notice Film properties that affect our output, and
211 alert listeners that our output now would be different to how it was
212 last time we were run.
215 if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) {
221 Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
223 list<PositionImage> all;
225 for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
230 dcpomatic::Rect<double> in_rect = (*i)->rectangle;
231 dcp::Size scaled_size;
233 in_rect.x += content->subtitle_x_offset ();
234 in_rect.y += content->subtitle_y_offset ();
236 /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
237 scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale ();
238 scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale ();
240 /* Then we need a corrective translation, consisting of two parts:
242 * 1. that which is the result of the scaling of the subtitle by _video_container_size; this will be
243 * rect.x * _video_container_size.width and rect.y * _video_container_size.height.
245 * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
246 * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
247 * (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
249 * Combining these two translations gives these expressions.
256 Scaler::from_id ("bicubic"),
257 (*i)->image->pixel_format (),
261 rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))),
262 rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2)))
272 Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
274 list<PositionImage> all;
275 for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = sub.begin(); i != sub.end(); ++i) {
276 if (!(*i)->subs.empty ()) {
277 all.push_back (render_subtitles ((*i)->subs, _video_container_size));
285 Player::set_approximate_size ()
287 _approximate_size = true;
291 Player::black_dcp_video (DCPTime time) const
293 return shared_ptr<DCPVideo> (
298 _video_container_size,
299 _video_container_size,
300 Scaler::from_id ("bicubic"),
301 Config::instance()->colour_conversions().front().conversion,
308 Player::content_to_dcp (
309 shared_ptr<VideoContent> content,
310 ContentVideo content_video,
311 list<shared_ptr<Piece> > subs,
313 dcp::Size image_size) const
315 shared_ptr<DCPVideo> dcp_video (
321 _video_container_size,
323 content->colour_conversion (),
331 list<PositionImage> sub_images;
333 for (list<shared_ptr<Piece> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
334 shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*i)->decoder);
335 shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*i)->content);
336 ContentTime const from = dcp_to_content_subtitle (*i, time);
337 ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ());
339 list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (from, to);
340 if (!image_subtitles.empty ()) {
341 list<PositionImage> im = process_content_image_subtitles (
346 copy (im.begin(), im.end(), back_inserter (sub_images));
349 if (_burn_subtitles) {
350 list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (from, to);
351 if (!text_subtitles.empty ()) {
352 list<PositionImage> im = process_content_text_subtitles (text_subtitles);
353 copy (im.begin(), im.end(), back_inserter (sub_images));
358 if (!sub_images.empty ()) {
359 dcp_video->set_subtitle (merge (sub_images));
365 /** @return All DCPVideo at the given time (there may be two frames for 3D) */
366 list<shared_ptr<DCPVideo> >
367 Player::get_video (DCPTime time, bool accurate)
369 if (!_have_valid_pieces) {
373 list<shared_ptr<Piece> > ov = overlaps<VideoContent> (
375 time + DCPTime::from_frames (1, _film->video_frame_rate ())
378 list<shared_ptr<DCPVideo> > dcp_video;
381 /* No video content at this time */
382 dcp_video.push_back (black_dcp_video (time));
386 /* Create a DCPVideo from the content's video at this time */
388 shared_ptr<Piece> piece = ov.back ();
389 shared_ptr<VideoDecoder> decoder = dynamic_pointer_cast<VideoDecoder> (piece->decoder);
391 shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
394 list<ContentVideo> content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate);
395 if (content_video.empty ()) {
396 dcp_video.push_back (black_dcp_video (time));
400 dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ());
401 if (_approximate_size) {
402 image_size.width &= ~3;
403 image_size.height &= ~3;
406 for (list<ContentVideo>::const_iterator i = content_video.begin(); i != content_video.end(); ++i) {
407 list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
409 time + DCPTime::from_frames (1, _film->video_frame_rate ())
412 dcp_video.push_back (content_to_dcp (content, *i, subs, time, image_size));
418 shared_ptr<AudioBuffers>
419 Player::get_audio (DCPTime time, DCPTime length, bool accurate)
421 if (!_have_valid_pieces) {
425 AudioFrame const length_frames = length.frames (_film->audio_frame_rate ());
427 shared_ptr<AudioBuffers> audio (new AudioBuffers (_film->audio_channels(), length_frames));
428 audio->make_silent ();
430 list<shared_ptr<Piece> > ov = overlaps<AudioContent> (time, time + length);
435 for (list<shared_ptr<Piece> >::iterator i = ov.begin(); i != ov.end(); ++i) {
437 shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> ((*i)->content);
439 shared_ptr<AudioDecoder> decoder = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
442 if (content->audio_frame_rate() == 0) {
443 /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no
449 /* The time that we should request from the content */
450 DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0);
452 if (request < DCPTime ()) {
453 /* We went off the start of the content, so we will need to offset
454 the stuff we get back.
457 request = DCPTime ();
460 AudioFrame const content_frame = dcp_to_content_audio (*i, request);
462 /* Audio from this piece's decoder (which might be more or less than what we asked for) */
463 shared_ptr<ContentAudio> all = decoder->get_audio (content_frame, length_frames, accurate);
466 if (content->audio_gain() != 0) {
467 shared_ptr<AudioBuffers> gain (new AudioBuffers (all->audio));
468 gain->apply_gain (content->audio_gain ());
473 shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), all->audio->frames()));
474 dcp_mapped->make_silent ();
475 AudioMapping map = content->audio_mapping ();
476 for (int i = 0; i < map.content_channels(); ++i) {
477 for (int j = 0; j < _film->audio_channels(); ++j) {
478 if (map.get (i, static_cast<dcp::Channel> (j)) > 0) {
479 dcp_mapped->accumulate_channel (
483 map.get (i, static_cast<dcp::Channel> (j))
489 all->audio = dcp_mapped;
491 audio->accumulate_frames (
493 content_frame - all->frame,
494 offset.frames (_film->audio_frame_rate()),
495 min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ())
503 Player::dcp_to_content_video (shared_ptr<const Piece> piece, DCPTime t) const
505 /* s is the offset of t from the start position of this content */
506 DCPTime s = t - piece->content->position ();
507 s = DCPTime (max (int64_t (0), s.get ()));
508 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
510 /* Convert this to the content frame */
511 return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor ();
515 Player::dcp_to_content_audio (shared_ptr<const Piece> piece, DCPTime t) const
517 /* s is the offset of t from the start position of this content */
518 DCPTime s = t - piece->content->position ();
519 s = DCPTime (max (int64_t (0), s.get ()));
520 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
522 /* Convert this to the content frame */
523 return DCPTime (s + piece->content->trim_start()).frames (_film->audio_frame_rate());
527 Player::dcp_to_content_subtitle (shared_ptr<const Piece> piece, DCPTime t) const
529 /* s is the offset of t from the start position of this content */
530 DCPTime s = t - piece->content->position ();
531 s = DCPTime (max (int64_t (0), s.get ()));
532 s = DCPTime (min (piece->content->length_after_trim().get(), s.get()));
534 return ContentTime (s, piece->frc);
538 PlayerStatistics::dump (shared_ptr<Log> log) const
540 log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
541 log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()));
544 PlayerStatistics const &
545 Player::statistics () const