X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fplayer.cc;h=f83c9563b29ce80cab262ad02c22c913219cf835;hb=3e12c68dc0451e73b5bc1a84d1d70f4999f7b4b5;hp=02ae4e5c91b1b94b77b40d51c199942ab4ef10a6;hpb=1eeba876ce09cedfa4c779bf3554372c01dc34c5;p=dcpomatic.git diff --git a/src/lib/player.cc b/src/lib/player.cc index 02ae4e5c9..f83c9563b 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -31,16 +31,25 @@ #include "subtitle_content.h" #include "subrip_decoder.h" #include "subrip_content.h" +#include "dcp_content.h" #include "playlist.h" #include "job.h" #include "image.h" +#include "raw_image_proxy.h" #include "ratio.h" #include "log.h" #include "scaler.h" #include "render_subtitles.h" -#include "dcp_video.h" #include "config.h" #include "content_video.h" +#include "player_video.h" +#include "frame_rate_change.h" +#include "dcp_content.h" +#include "dcp_decoder.h" +#include "dcp_subtitle_content.h" +#include "dcp_subtitle_decoder.h" + +#define LOG_GENERAL(...) _film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL); using std::list; using std::cout; @@ -61,7 +70,6 @@ Player::Player (shared_ptr f, shared_ptr p) , _playlist (p) , _have_valid_pieces (false) , _approximate_size (false) - , _burn_subtitles (false) { _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); @@ -117,6 +125,12 @@ Player::setup_pieces () frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } + shared_ptr dc = dynamic_pointer_cast (*i); + if (dc) { + decoder.reset (new DCPDecoder (dc, _film->log ())); + frc = FrameRateChange (dc->video_frame_rate(), _film->video_frame_rate()); + } + /* ImageContent */ shared_ptr ic = dynamic_pointer_cast (*i); if (ic) { @@ -149,6 +163,13 @@ Player::setup_pieces () frc = best_overlap_frc; } + /* DCPSubtitleContent */ + shared_ptr dsc = dynamic_pointer_cast (*i); + if (dsc) { + decoder.reset (new DCPSubtitleDecoder (dsc)); + frc = best_overlap_frc; + } + _pieces.push_back (shared_ptr (new Piece (*i, decoder, frc.get ()))); } @@ -169,16 +190,19 @@ Player::content_changed (weak_ptr w, int property, bool frequent) property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END || property == ContentProperty::PATH || - property == VideoContentProperty::VIDEO_FRAME_TYPE + property == VideoContentProperty::VIDEO_FRAME_TYPE || + property == DCPContentProperty::CAN_BE_PLAYED ) { _have_valid_pieces = false; Changed (frequent); } else if ( + property == SubtitleContentProperty::USE_SUBTITLES || property == SubtitleContentProperty::SUBTITLE_X_OFFSET || property == SubtitleContentProperty::SUBTITLE_Y_OFFSET || - property == SubtitleContentProperty::SUBTITLE_SCALE || + property == SubtitleContentProperty::SUBTITLE_X_SCALE || + property == SubtitleContentProperty::SUBTITLE_Y_SCALE || property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_SCALE || property == VideoContentProperty::VIDEO_FRAME_RATE @@ -188,6 +212,7 @@ Player::content_changed (weak_ptr w, int property, bool frequent) } } +/** @param already_resampled true if this data has already been through the chain up to the resampler */ void Player::playlist_changed () { @@ -212,30 +237,23 @@ Player::film_changed (Film::Property p) last time we were run. */ - if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { + if (p == Film::SCALER || p == Film::CONTAINER || p == Film::VIDEO_FRAME_RATE) { Changed (false); } } list -Player::process_content_image_subtitles (shared_ptr content, list > subs) +Player::transform_image_subtitles (list subs) const { list all; - for (list >::const_iterator i = subs.begin(); i != subs.end(); ++i) { - if (!(*i)->image) { + for (list::const_iterator i = subs.begin(); i != subs.end(); ++i) { + if (!i->image) { continue; } - dcpomatic::Rect in_rect = (*i)->rectangle; - dcp::Size scaled_size; - - in_rect.x += content->subtitle_x_offset (); - in_rect.y += content->subtitle_y_offset (); - - /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */ - scaled_size.width = in_rect.width * _video_container_size.width * content->subtitle_scale (); - scaled_size.height = in_rect.height * _video_container_size.height * content->subtitle_scale (); + /* We will scale the subtitle up to fit _video_container_size */ + dcp::Size scaled_size (i->rectangle.width * _video_container_size.width, i->rectangle.height * _video_container_size.height); /* Then we need a corrective translation, consisting of two parts: * @@ -243,23 +261,23 @@ Player::process_content_image_subtitles (shared_ptr content, li * rect.x * _video_container_size.width and rect.y * _video_container_size.height. * * 2. that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be - * (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and - * (height_before_subtitle_scale * (1 - subtitle_scale) / 2). + * (width_before_subtitle_scale * (1 - subtitle_x_scale) / 2) and + * (height_before_subtitle_scale * (1 - subtitle_y_scale) / 2). * * Combining these two translations gives these expressions. */ all.push_back ( PositionImage ( - (*i)->image->scale ( + i->image->scale ( scaled_size, Scaler::from_id ("bicubic"), - (*i)->image->pixel_format (), + i->image->pixel_format (), true ), Position ( - rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - content->subtitle_scale ()) / 2))), - rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - content->subtitle_scale ()) / 2))) + rint (_video_container_size.width * i->rectangle.x), + rint (_video_container_size.height * i->rectangle.y) ) ) ); @@ -268,114 +286,108 @@ Player::process_content_image_subtitles (shared_ptr content, li return all; } -list -Player::process_content_text_subtitles (list > sub) -{ - list all; - for (list >::const_iterator i = sub.begin(); i != sub.end(); ++i) { - if (!(*i)->subs.empty ()) { - all.push_back (render_subtitles ((*i)->subs, _video_container_size)); - } - } - - return all; -} - void Player::set_approximate_size () { _approximate_size = true; } -shared_ptr +shared_ptr +Player::black_player_video_frame (DCPTime time) const +{ + return shared_ptr ( + new PlayerVideo ( + shared_ptr (new RawImageProxy (_black_image, _film->log ())), + time, + Crop (), + _video_container_size, + _video_container_size, + Scaler::from_id ("bicubic"), + EYES_BOTH, + PART_WHOLE, + Config::instance()->colour_conversions().front().conversion + ) + ); +} + +/** @return All PlayerVideos at the given time (there may be two frames for 3D) */ +list > Player::get_video (DCPTime time, bool accurate) { if (!_have_valid_pieces) { setup_pieces (); } - list > ov = overlaps (time); - if (ov.empty ()) { - /* No video content at this time: return a black frame */ - return shared_ptr ( - new DCPVideo ( - _black_image, - EYES_BOTH, - Crop (), - _video_container_size, - _video_container_size, - Scaler::from_id ("bicubic"), - Config::instance()->colour_conversions().front().conversion, - time - ) - ); - } + list > ov = overlaps ( + time, + time + DCPTime::from_frames (1, _film->video_frame_rate ()) + ); - /* Create a DCPVideo from the content's video at this time */ + list > pvf; - shared_ptr piece = ov.back (); - shared_ptr decoder = dynamic_pointer_cast (piece->decoder); - assert (decoder); - shared_ptr content = dynamic_pointer_cast (piece->content); - assert (content); + if (ov.empty ()) { + /* No video content at this time */ + pvf.push_back (black_player_video_frame (time)); + } else { + /* Create a PlayerVideo from the content's video at this time */ - optional dec = decoder->get_video (dcp_to_content_video (piece, time), accurate); - assert (dec); + shared_ptr piece = ov.back (); + shared_ptr decoder = dynamic_pointer_cast (piece->decoder); + assert (decoder); + shared_ptr content = dynamic_pointer_cast (piece->content); + assert (content); - dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size ()); - if (_approximate_size) { - image_size.width &= ~3; - image_size.height &= ~3; + list content_video = decoder->get_video (dcp_to_content_video (piece, time), accurate); + if (content_video.empty ()) { + pvf.push_back (black_player_video_frame (time)); + return pvf; + } + + dcp::Size image_size = content->scale().size (content, _video_container_size, _film->frame_size (), _approximate_size ? 4 : 1); + if (_approximate_size) { + image_size.width &= ~3; + image_size.height &= ~3; + } + + for (list::const_iterator i = content_video.begin(); i != content_video.end(); ++i) { + pvf.push_back ( + shared_ptr ( + new PlayerVideo ( + i->image, + content_video_to_dcp (piece, i->frame), + content->crop (), + image_size, + _video_container_size, + _film->scaler(), + i->eyes, + i->part, + content->colour_conversion () + ) + ) + ); + } } - shared_ptr dcp_video ( - new DCPVideo ( - dec->image, - dec->eyes, - content->crop (), - image_size, - _video_container_size, - _film->scaler(), - content->colour_conversion (), - time - ) - ); + /* Add subtitles (for possible burn-in) to whatever PlayerVideos we got */ - /* Add subtitles */ + PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()), false); - ov = overlaps (time); list sub_images; - - for (list >::const_iterator i = ov.begin(); i != ov.end(); ++i) { - shared_ptr subtitle_decoder = dynamic_pointer_cast ((*i)->decoder); - shared_ptr subtitle_content = dynamic_pointer_cast ((*i)->content); - ContentTime const from = dcp_to_content_subtitle (*i, time); - ContentTime const to = from + ContentTime::from_frames (1, content->video_frame_rate ()); - - list > image_subtitles = subtitle_decoder->get_image_subtitles (from, to); - if (!image_subtitles.empty ()) { - list im = process_content_image_subtitles ( - subtitle_content, - image_subtitles - ); - - copy (im.begin(), im.end(), back_inserter (sub_images)); - } - if (_burn_subtitles) { - list > text_subtitles = subtitle_decoder->get_text_subtitles (from, to); - if (!text_subtitles.empty ()) { - list im = process_content_text_subtitles (text_subtitles); - copy (im.begin(), im.end(), back_inserter (sub_images)); - } - } - } + /* Image subtitles */ + list c = transform_image_subtitles (ps.image); + copy (c.begin(), c.end(), back_inserter (sub_images)); + /* Text subtitles (rendered to images) */ + sub_images.push_back (render_subtitles (ps.text, _video_container_size)); + if (!sub_images.empty ()) { - dcp_video->set_subtitle (merge (sub_images)); - } - - return dcp_video; + for (list >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) { + (*i)->set_subtitle (merge (sub_images)); + } + } + + return pvf; } shared_ptr @@ -390,7 +402,7 @@ Player::get_audio (DCPTime time, DCPTime length, bool accurate) shared_ptr audio (new AudioBuffers (_film->audio_channels(), length_frames)); audio->make_silent (); - list > ov = overlaps (time); + list > ov = overlaps (time, time + length); if (ov.empty ()) { return audio; } @@ -402,10 +414,28 @@ Player::get_audio (DCPTime time, DCPTime length, bool accurate) shared_ptr decoder = dynamic_pointer_cast ((*i)->decoder); assert (decoder); - AudioFrame const content_time = dcp_to_content_audio (*i, time); + if (content->audio_frame_rate() == 0) { + /* This AudioContent has no audio (e.g. if it is an FFmpegContent with no + * audio stream). + */ + continue; + } + + /* The time that we should request from the content */ + DCPTime request = time - DCPTime::from_seconds (content->audio_delay() / 1000.0); + DCPTime offset; + if (request < DCPTime ()) { + /* We went off the start of the content, so we will need to offset + the stuff we get back. + */ + offset = -request; + request = DCPTime (); + } + + AudioFrame const content_frame = dcp_to_content_audio (*i, request); - /* Audio from this piece's decoder (which might be more than what we asked for) */ - shared_ptr all = decoder->get_audio (content_time, length_frames, accurate); + /* Audio from this piece's decoder (which might be more or less than what we asked for) */ + shared_ptr all = decoder->get_audio (content_frame, length_frames, accurate); /* Gain */ if (content->audio_gain() != 0) { @@ -432,25 +462,13 @@ Player::get_audio (DCPTime time, DCPTime length, bool accurate) } all->audio = dcp_mapped; - - /* Delay */ - /* XXX - audio->dcp_time += content->audio_delay() * TIME_HZ / 1000; - if (audio->dcp_time < 0) { - int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ; - if (frames >= audio->audio->frames ()) { - return; - } - - shared_ptr trimmed (new AudioBuffers (audio->audio->channels(), audio->audio->frames() - frames)); - trimmed->copy_from (audio->audio.get(), audio->audio->frames() - frames, frames, 0); - - audio->audio = trimmed; - audio->dcp_time = 0; - } - */ - audio->accumulate_frames (all->audio.get(), all->frame - content_time, 0, min (AudioFrame (all->audio->frames()), length_frames)); + audio->accumulate_frames ( + all->audio.get(), + content_frame - all->frame, + offset.frames (_film->audio_frame_rate()), + min (AudioFrame (all->audio->frames()), length_frames) - offset.frames (_film->audio_frame_rate ()) + ); } return audio; @@ -461,19 +479,30 @@ Player::dcp_to_content_video (shared_ptr piece, DCPTime t) const { /* s is the offset of t from the start position of this content */ DCPTime s = t - piece->content->position (); - s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (max (DCPTime::Type (0), s.get ())); s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); /* Convert this to the content frame */ return DCPTime (s + piece->content->trim_start()).frames (_film->video_frame_rate()) * piece->frc.factor (); } +DCPTime +Player::content_video_to_dcp (shared_ptr piece, VideoFrame f) const +{ + DCPTime t = DCPTime::from_frames (f / piece->frc.factor (), _film->video_frame_rate()) - piece->content->trim_start () + piece->content->position (); + if (t < DCPTime ()) { + t = DCPTime (); + } + + return t; +} + AudioFrame Player::dcp_to_content_audio (shared_ptr piece, DCPTime t) const { /* s is the offset of t from the start position of this content */ DCPTime s = t - piece->content->position (); - s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (max (DCPTime::Type (0), s.get ())); s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); /* Convert this to the content frame */ @@ -485,17 +514,17 @@ Player::dcp_to_content_subtitle (shared_ptr piece, DCPTime t) const { /* s is the offset of t from the start position of this content */ DCPTime s = t - piece->content->position (); - s = DCPTime (max (int64_t (0), s.get ())); + s = DCPTime (max (DCPTime::Type (0), s.get ())); s = DCPTime (min (piece->content->length_after_trim().get(), s.get())); - return ContentTime (s, piece->frc); + return ContentTime (s + piece->content->trim_start(), piece->frc); } void PlayerStatistics::dump (shared_ptr log) const { - log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat)); - log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds())); + log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat), Log::TYPE_GENERAL); + log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence.seconds()), Log::TYPE_GENERAL); } PlayerStatistics const & @@ -503,3 +532,48 @@ Player::statistics () const { return _statistics; } + +PlayerSubtitles +Player::get_subtitles (DCPTime time, DCPTime length, bool starting) +{ + list > subs = overlaps (time, time + length); + + PlayerSubtitles ps (time, length); + + for (list >::const_iterator j = subs.begin(); j != subs.end(); ++j) { + shared_ptr subtitle_content = dynamic_pointer_cast ((*j)->content); + if (!subtitle_content->use_subtitles ()) { + continue; + } + + shared_ptr subtitle_decoder = dynamic_pointer_cast ((*j)->decoder); + ContentTime const from = dcp_to_content_subtitle (*j, time); + /* XXX: this video_frame_rate() should be the rate that the subtitle content has been prepared for */ + ContentTime const to = from + ContentTime::from_frames (1, _film->video_frame_rate ()); + + list image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to), starting); + for (list::iterator i = image.begin(); i != image.end(); ++i) { + + /* Apply content's subtitle offsets */ + i->sub.rectangle.x += subtitle_content->subtitle_x_offset (); + i->sub.rectangle.y += subtitle_content->subtitle_y_offset (); + + /* Apply content's subtitle scale */ + i->sub.rectangle.width *= subtitle_content->subtitle_x_scale (); + i->sub.rectangle.height *= subtitle_content->subtitle_y_scale (); + + /* Apply a corrective translation to keep the subtitle centred after that scale */ + i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_x_scale() - 1); + i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_y_scale() - 1); + + ps.image.push_back (i->sub); + } + + list text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to), starting); + for (list::const_iterator i = text.begin(); i != text.end(); ++i) { + copy (i->subs.begin(), i->subs.end(), back_inserter (ps.text)); + } + } + + return ps; +}