Play PNG subtitles from DCPs; possibly not in the right scale.
authorCarl Hetherington <cth@carlh.net>
Sun, 2 Sep 2018 00:11:11 +0000 (01:11 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 2 Sep 2018 00:11:11 +0000 (01:11 +0100)
src/lib/dcp_decoder.cc
src/lib/dcp_decoder.h
src/lib/image.cc
src/lib/image.h
src/lib/text_decoder.cc
src/lib/text_decoder.h

index f4da18649e2a01bae620ed791e5020f499c53df2..4e595da43f41e744b7ee0f9c1f4d7d4b7ee8795c 100644 (file)
@@ -42,6 +42,7 @@
 #include <dcp/stereo_picture_frame.h>
 #include <dcp/sound_frame.h>
 #include <dcp/sound_asset_reader.h>
+#include <dcp/subtitle_image.h>
 #include <boost/foreach.hpp>
 #include <iostream>
 
@@ -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<dcp::PictureAsset> 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<dcp::PictureAsset> asset = (*_reel)->main_picture()->asset ();
                int64_t const entry_point = (*_reel)->main_picture()->entry_point ();
                if (_mono_reader) {
                        video->emit (
                                shared_ptr<ImageProxy> (
                                        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<ImageProxy> (
                                        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<ImageProxy> (
                                        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<shared_ptr<TextDecoder> >::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<dcp::ReelClosedCaptionAsset> 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<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, shared_ptr<TextDecoder> decoder)
+DCPDecoder::pass_texts (ContentTime next, shared_ptr<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, shared_ptr<TextDecoder> 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<dcp::SubtitleAsset> asset,
                                        );
                        }
 
-                       /* XXX: image subtitles */
+                       shared_ptr<dcp::SubtitleImage> ii = dynamic_pointer_cast<dcp::SubtitleImage> (i);
+                       if (ii) {
+                               shared_ptr<Image> image(new Image(ii->png_image()));
+                               /* set up rect with height and width */
+                               dcpomatic::Rect<double> 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);
        }
 
index 36b5bbafb049727d806a943053d0c4585aceaad9..8281babc3a0a92c985c1448d706eadaaa5d332fe 100644 (file)
@@ -57,8 +57,10 @@ private:
 
        void next_reel ();
        void get_readers ();
-       void pass_texts (ContentTime next);
-       void pass_texts (ContentTime next, boost::shared_ptr<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, boost::shared_ptr<TextDecoder> decoder);
+       void pass_texts (ContentTime next, dcp::Size size);
+       void pass_texts (
+               ContentTime next, boost::shared_ptr<dcp::SubtitleAsset> asset, bool reference, int64_t entry_point, boost::shared_ptr<TextDecoder> decoder, dcp::Size size
+               );
 
        /** Time of next thing to return from pass relative to the start of _reel */
        ContentTime _next;
index 08507ec5f8d8e1517d4bd380c8e50885dc28e22e..b75c0f083bd9bcb5cedcd423d388aea5e1df760b 100644 (file)
@@ -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 ()
 {
index 73f2313c1a03f297c22960d5ba290aa83d927fb9..1869ca828fef65f6bedff70cd1105054e0d741ce 100644 (file)
@@ -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<const Image>, bool);
        Image& operator= (Image const &);
        ~Image ();
index 00d58af869f0135fa45d4d00388f02d734623453..d6cf517c2e04393706ffcf81593c24fc91598dda 100644 (file)
@@ -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> image, dcpomatic::Rect<double> rect)
+{
+       emit_bitmap_start (period.from, image, rect);
+       emit_stop (period.to);
+}
+
 void
 TextDecoder::seek ()
 {
index d8a64157e9f7f96ea48456312a6818ce34cff8f0..d45e37fc7c78817abc30f4d1e739ab920156c04f 100644 (file)
@@ -50,6 +50,7 @@ public:
        }
 
        void emit_bitmap_start (ContentTime from, boost::shared_ptr<Image> image, dcpomatic::Rect<double> rect);
+       void emit_bitmap (ContentTimePeriod period, boost::shared_ptr<Image> image, dcpomatic::Rect<double> rect);
        void emit_plain_start (ContentTime from, std::list<dcp::SubtitleString> s);
        void emit_plain_start (ContentTime from, sub::Subtitle const & subtitle);
        void emit_plain (ContentTimePeriod period, std::list<dcp::SubtitleString> s);