From a18e837cb56da42a500a438a09afcbddad8b3a94 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 2 Sep 2018 01:11:11 +0100 Subject: [PATCH] Play PNG subtitles from DCPs; possibly not in the right scale. --- src/lib/dcp_decoder.cc | 64 ++++++++++++++++++++++++++++++++++------- src/lib/dcp_decoder.h | 6 ++-- src/lib/image.cc | 28 ++++++++++++++++++ src/lib/image.h | 1 + src/lib/text_decoder.cc | 11 +++++++ src/lib/text_decoder.h | 1 + 6 files changed, 98 insertions(+), 13 deletions(-) diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index f4da18649..4e595da43 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -110,20 +111,22 @@ DCPDecoder::pass () /* Frame within the (played part of the) reel that is coming up next */ int64_t const frame = _next.frames_round (vfr); + shared_ptr picture_asset = (*_reel)->main_picture()->asset(); + DCPOMATIC_ASSERT (picture_asset); + /* We must emit texts first as when we emit the video for this frame it will expect already to have the texts. */ - pass_texts (_next); + pass_texts (_next, picture_asset->size()); if ((_mono_reader || _stereo_reader) && (_decode_referenced || !_dcp_content->reference_video())) { - shared_ptr asset = (*_reel)->main_picture()->asset (); int64_t const entry_point = (*_reel)->main_picture()->entry_point (); if (_mono_reader) { video->emit ( shared_ptr ( new J2KImageProxy ( _mono_reader->get_frame (entry_point + frame), - asset->size(), + picture_asset->size(), AV_PIX_FMT_XYZ12LE, _forced_reduction ) @@ -135,7 +138,7 @@ DCPDecoder::pass () shared_ptr ( new J2KImageProxy ( _stereo_reader->get_frame (entry_point + frame), - asset->size(), + picture_asset->size(), dcp::EYE_LEFT, AV_PIX_FMT_XYZ12LE, _forced_reduction @@ -148,7 +151,7 @@ DCPDecoder::pass () shared_ptr ( new J2KImageProxy ( _stereo_reader->get_frame (entry_point + frame), - asset->size(), + picture_asset->size(), dcp::EYE_RIGHT, AV_PIX_FMT_XYZ12LE, _forced_reduction @@ -191,27 +194,27 @@ DCPDecoder::pass () } void -DCPDecoder::pass_texts (ContentTime next) +DCPDecoder::pass_texts (ContentTime next, dcp::Size size) { list >::const_iterator decoder = text.begin (); if ((*_reel)->main_subtitle()) { DCPOMATIC_ASSERT (decoder != text.end ()); pass_texts ( - next, (*_reel)->main_subtitle()->asset(), _dcp_content->reference_text(TEXT_OPEN_SUBTITLE), (*_reel)->main_subtitle()->entry_point(), *decoder + next, (*_reel)->main_subtitle()->asset(), _dcp_content->reference_text(TEXT_OPEN_SUBTITLE), (*_reel)->main_subtitle()->entry_point(), *decoder, size ); ++decoder; } BOOST_FOREACH (shared_ptr i, (*_reel)->closed_captions()) { DCPOMATIC_ASSERT (decoder != text.end ()); pass_texts ( - next, i->asset(), _dcp_content->reference_text(TEXT_CLOSED_CAPTION), i->entry_point(), *decoder + next, i->asset(), _dcp_content->reference_text(TEXT_CLOSED_CAPTION), i->entry_point(), *decoder, size ); ++decoder; } } void -DCPDecoder::pass_texts (ContentTime next, shared_ptr asset, bool reference, int64_t entry_point, shared_ptr decoder) +DCPDecoder::pass_texts (ContentTime next, shared_ptr asset, bool reference, int64_t entry_point, shared_ptr decoder, dcp::Size size) { double const vfr = _dcp_content->active_video_frame_rate (); /* Frame within the (played part of the) reel that is coming up next */ @@ -238,7 +241,46 @@ DCPDecoder::pass_texts (ContentTime next, shared_ptr asset, ); } - /* XXX: image subtitles */ + shared_ptr ii = dynamic_pointer_cast (i); + if (ii) { + shared_ptr image(new Image(ii->png_image())); + /* set up rect with height and width */ + dcpomatic::Rect rect(0, 0, image->size().width / double(size.width), image->size().height / double(size.height)); + + /* add in position */ + + switch (ii->h_align()) { + case dcp::HALIGN_LEFT: + rect.x += ii->h_position(); + break; + case dcp::HALIGN_CENTER: + rect.x += 0.5 + ii->h_position() - rect.width / 2; + break; + case dcp::HALIGN_RIGHT: + rect.x += 1 - ii->h_position() - rect.width; + break; + } + + switch (ii->v_align()) { + case dcp::VALIGN_TOP: + rect.y += ii->v_position(); + break; + case dcp::VALIGN_CENTER: + rect.y += 0.5 + ii->v_position() - rect.height / 2; + break; + case dcp::VALIGN_BOTTOM: + rect.y += 1 - ii->v_position() - rect.height; + break; + } + + decoder->emit_bitmap ( + ContentTimePeriod ( + ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (i->in().as_seconds ()), + ContentTime::from_frames (_offset - entry_point, vfr) + ContentTime::from_seconds (i->out().as_seconds ()) + ), + image, rect + ); + } } } } @@ -320,7 +362,7 @@ DCPDecoder::seek (ContentTime t, bool accurate) double const vfr = _dcp_content->active_video_frame_rate (); for (int i = 0; i < pre_roll_seconds * vfr; ++i) { - pass_texts (pre); + pass_texts (pre, (*_reel)->main_picture()->asset()->size()); pre += ContentTime::from_frames (1, vfr); } diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index 36b5bbafb..8281babc3 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -57,8 +57,10 @@ private: void next_reel (); void get_readers (); - void pass_texts (ContentTime next); - void pass_texts (ContentTime next, boost::shared_ptr asset, bool reference, int64_t entry_point, boost::shared_ptr decoder); + void pass_texts (ContentTime next, dcp::Size size); + void pass_texts ( + ContentTime next, boost::shared_ptr asset, bool reference, int64_t entry_point, boost::shared_ptr decoder, dcp::Size size + ); /** Time of next thing to return from pass relative to the start of _reel */ ContentTime _next; diff --git a/src/lib/image.cc b/src/lib/image.cc index 08507ec5f..b75c0f083 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -791,6 +791,34 @@ Image::Image (AVPixelFormat p, dcp::Size s, bool aligned, int extra_pixels) allocate (); } +/** Construct an Image from some PNG data */ +Image::Image (dcp::Data png) +{ + Magick::Blob blob; + blob.update (png.data().get(), png.size()); + Magick::Image* magick_image = new Magick::Image (blob); + _size = dcp::Size(magick_image->columns(), magick_image->rows()); + _pixel_format = AV_PIX_FMT_BGRA; + _aligned = true; + _extra_pixels = 0; + allocate (); + + /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */ + uint8_t* p = data()[0]; + for (int i = 0; i < _size.height; ++i) { +#ifdef DCPOMATIC_HAVE_MAGICKCORE_NAMESPACE + using namespace MagickCore; +#endif +#ifdef DCPOMATIC_HAVE_MAGICKLIB_NAMESPACE + using namespace MagickLib; +#endif + magick_image->write (0, i, _size.width, 1, "BGRA", CharPixel, p); + p += stride()[0]; + } + + delete magick_image; +} + void Image::allocate () { diff --git a/src/lib/image.h b/src/lib/image.h index 73f2313c1..1869ca828 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -44,6 +44,7 @@ public: Image (AVPixelFormat p, dcp::Size s, bool aligned, int extra_pixels = 0); explicit Image (AVFrame *); explicit Image (Image const &); + explicit Image (dcp::Data); Image (boost::shared_ptr, bool); Image& operator= (Image const &); ~Image (); diff --git a/src/lib/text_decoder.cc b/src/lib/text_decoder.cc index 00d58af86..d6cf517c2 100644 --- a/src/lib/text_decoder.cc +++ b/src/lib/text_decoder.cc @@ -250,6 +250,17 @@ TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s) emit_stop (period.to); } +/* @param rect Area expressed as a fraction of the video frame that this subtitle + * is for (e.g. a width of 0.5 means the width of the subtitle is half the width + * of the video frame) + */ +void +TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr image, dcpomatic::Rect rect) +{ + emit_bitmap_start (period.from, image, rect); + emit_stop (period.to); +} + void TextDecoder::seek () { diff --git a/src/lib/text_decoder.h b/src/lib/text_decoder.h index d8a64157e..d45e37fc7 100644 --- a/src/lib/text_decoder.h +++ b/src/lib/text_decoder.h @@ -50,6 +50,7 @@ public: } void emit_bitmap_start (ContentTime from, boost::shared_ptr image, dcpomatic::Rect rect); + void emit_bitmap (ContentTimePeriod period, boost::shared_ptr image, dcpomatic::Rect rect); void emit_plain_start (ContentTime from, std::list s); void emit_plain_start (ContentTime from, sub::Subtitle const & subtitle); void emit_plain (ContentTimePeriod period, std::list s); -- 2.30.2