Rearrange Player subtitle handling a bit.
authorCarl Hetherington <cth@carlh.net>
Wed, 2 Jul 2014 13:19:21 +0000 (14:19 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 2 Jul 2014 13:19:21 +0000 (14:19 +0100)
src/lib/content_subtitle.h
src/lib/image_subtitle.h [new file with mode: 0644]
src/lib/player.cc
src/lib/player.h
src/lib/player_subtitles.h [new file with mode: 0644]
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/wx/subtitle_view.cc
test/subrip_test.cc

index 6a28c37bf2c79b19eb3ffde4040f66559c6b277a..8868618ada940ccf883f552dbf075ab449fab3bb 100644 (file)
@@ -24,6 +24,7 @@
 #include <dcp/subtitle_string.h>
 #include "dcpomatic_time.h"
 #include "rect.h"
+#include "image_subtitle.h"
 
 class Image;
 
@@ -37,8 +38,7 @@ class ContentImageSubtitle : public ContentSubtitle
 {
 public:
        ContentImageSubtitle (ContentTimePeriod p, boost::shared_ptr<Image> im, dcpomatic::Rect<double> r)
-               : image (im)
-               , rectangle (r)
+               : sub (im, r)
                , _period (p)
        {}
 
@@ -46,8 +46,8 @@ public:
                return _period;
        }
 
-       boost::shared_ptr<Image> image;
-       dcpomatic::Rect<double> rectangle;
+       /* Our subtitle, with its rectangle unmodified by any offsets or scales that the content specifies */
+       ImageSubtitle sub;
 
 private:
        ContentTimePeriod _period;
diff --git a/src/lib/image_subtitle.h b/src/lib/image_subtitle.h
new file mode 100644 (file)
index 0000000..6a4f902
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+class Image;
+
+class ImageSubtitle
+{
+public:
+       ImageSubtitle (boost::shared_ptr<Image> i, dcpomatic::Rect<double> r)
+               : image (i)
+               , rectangle (r)
+       {}
+       
+       boost::shared_ptr<Image> image;
+       /** Area that the subtitle covers on its corresponding video, expressed in
+        *  proportions of the image size; e.g. rectangle.x = 0.5 would mean that
+        *  the rectangle starts half-way across the video.
+        *
+        *  This rectangle may or may not have had a SubtitleContent's offsets and
+        *  scale applied to it, depending on context.
+        */
+       dcpomatic::Rect<double> rectangle;
+};
index 9257191693aab11bd89a2c120227071cd51bc6b5..ccc1ab2367e3e0fed2832790c4d97ccc9b9ce2db 100644 (file)
@@ -223,24 +223,17 @@ Player::film_changed (Film::Property p)
 }
 
 list<PositionImage>
-Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, list<shared_ptr<ContentImageSubtitle> > subs) const
+Player::transform_image_subtitles (list<ImageSubtitle> subs) const
 {
        list<PositionImage> all;
        
-       for (list<shared_ptr<ContentImageSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
-               if (!(*i)->image) {
+       for (list<ImageSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               if (!i->image) {
                        continue;
                }
 
-               dcpomatic::Rect<double> 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:
                 *
@@ -256,15 +249,15 @@ Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, li
 
                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<int> (
-                                       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)
                                        )
                                )
                        );
@@ -273,19 +266,6 @@ Player::process_content_image_subtitles (shared_ptr<SubtitleContent> content, li
        return all;
 }
 
-list<PositionImage>
-Player::process_content_text_subtitles (list<shared_ptr<ContentTextSubtitle> > sub) const
-{
-       list<PositionImage> all;
-       for (list<shared_ptr<ContentTextSubtitle> >::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 ()
 {
@@ -368,42 +348,18 @@ Player::get_video (DCPTime time, bool accurate)
                }
        }
 
-       /* Add subtitles to whatever PlayerVideos we got */
-       
-       list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (
-               time,
-               time + DCPTime::from_frames (1, _film->video_frame_rate ())
-               );
+       /* Add subtitles (for possible burn-in) to whatever PlayerVideos we got */
+
+       PlayerSubtitles ps = get_subtitles (time, DCPTime::from_frames (1, _film->video_frame_rate ()));
 
        list<PositionImage> sub_images;
-       
-       for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
-               shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
-               if (!subtitle_content->subtitle_use ()) {
-                       continue;
-               }
 
-               shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*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 ());
+       /* Image subtitles */
+       list<PositionImage> c = transform_image_subtitles (ps.image);
+       copy (c.begin(), c.end(), back_inserter (sub_images));
 
-               list<shared_ptr<ContentImageSubtitle> > image_subtitles = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to));
-               if (!image_subtitles.empty ()) {
-                       list<PositionImage> im = process_content_image_subtitles (
-                               subtitle_content,
-                               image_subtitles
-                               );
-                       
-                       copy (im.begin(), im.end(), back_inserter (sub_images));
-               }
-               
-               list<shared_ptr<ContentTextSubtitle> > text_subtitles = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to));
-               if (!text_subtitles.empty ()) {
-                       list<PositionImage> im = process_content_text_subtitles (text_subtitles);
-                       copy (im.begin(), im.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 ()) {
                for (list<shared_ptr<PlayerVideo> >::const_iterator i = pvf.begin(); i != pvf.end(); ++i) {
@@ -556,3 +512,48 @@ Player::statistics () const
 {
        return _statistics;
 }
+
+PlayerSubtitles
+Player::get_subtitles (DCPTime time, DCPTime length)
+{
+       list<shared_ptr<Piece> > subs = overlaps<SubtitleContent> (time, time + length);
+
+       PlayerSubtitles ps (time, length);
+
+       for (list<shared_ptr<Piece> >::const_iterator j = subs.begin(); j != subs.end(); ++j) {
+               shared_ptr<SubtitleContent> subtitle_content = dynamic_pointer_cast<SubtitleContent> ((*j)->content);
+               if (!subtitle_content->subtitle_use ()) {
+                       continue;
+               }
+
+               shared_ptr<SubtitleDecoder> subtitle_decoder = dynamic_pointer_cast<SubtitleDecoder> ((*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<ContentImageSubtitle> image = subtitle_decoder->get_image_subtitles (ContentTimePeriod (from, to));
+               for (list<ContentImageSubtitle>::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_scale ();
+                       i->sub.rectangle.height *= subtitle_content->subtitle_scale ();
+
+                       /* Apply a corrective translation to keep the subtitle centred after that scale */
+                       i->sub.rectangle.x -= i->sub.rectangle.width * (subtitle_content->subtitle_scale() - 1);
+                       i->sub.rectangle.y -= i->sub.rectangle.height * (subtitle_content->subtitle_scale() - 1);
+                       
+                       ps.image.push_back (i->sub);
+               }
+
+               list<ContentTextSubtitle> text = subtitle_decoder->get_text_subtitles (ContentTimePeriod (from, to));
+               for (list<ContentTextSubtitle>::const_iterator i = text.begin(); i != text.end(); ++i) {
+                       copy (i->subs.begin(), i->subs.end(), back_inserter (ps.text));
+               }
+       }
+
+       return ps;
+}
index 3ee909f97b53718c86a4602811a14d49b3220379..0c58af44503c7588e20db0eeab0c62418f88f705 100644 (file)
@@ -33,6 +33,7 @@
 #include "position_image.h"
 #include "piece.h"
 #include "content_video.h"
+#include "player_subtitles.h"
 
 class Job;
 class Film;
@@ -87,6 +88,7 @@ public:
 
        std::list<boost::shared_ptr<PlayerVideo> > get_video (DCPTime time, bool accurate);
        boost::shared_ptr<AudioBuffers> get_audio (DCPTime time, DCPTime length, bool accurate);
+       PlayerSubtitles get_subtitles (DCPTime time, DCPTime length);
 
        void set_video_container_size (dcp::Size);
        void set_approximate_size ();
@@ -111,10 +113,7 @@ private:
        void content_changed (boost::weak_ptr<Content>, int, bool);
        void flush ();
        void film_changed (Film::Property);
-       std::list<PositionImage> process_content_image_subtitles (
-               boost::shared_ptr<SubtitleContent>, std::list<boost::shared_ptr<ContentImageSubtitle> >
-               ) const;
-       std::list<PositionImage> process_content_text_subtitles (std::list<boost::shared_ptr<ContentTextSubtitle> >) const;
+       std::list<PositionImage> transform_image_subtitles (std::list<ImageSubtitle>) const;
        void update_subtitle_from_text ();
        VideoFrame dcp_to_content_video (boost::shared_ptr<const Piece> piece, DCPTime t) const;
        DCPTime content_video_to_dcp (boost::shared_ptr<const Piece> piece, VideoFrame f) const;
diff --git a/src/lib/player_subtitles.h b/src/lib/player_subtitles.h
new file mode 100644 (file)
index 0000000..62b77c0
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+class PlayerSubtitles
+{
+public:
+       PlayerSubtitles (DCPTime f, DCPTime t)
+               : from (f)
+               , to (t)
+       {}
+       
+       DCPTime from;
+       DCPTime to;
+
+       /** ImageSubtitles, with their rectangles transformed as specified by their content */
+       std::list<ImageSubtitle> image;
+       std::list<dcp::SubtitleString> text; 
+};
index 786199e31a681d47db0f3b27cd9efad00de13208..1b789fdab478e5cde0200a64f7a0e92fdb2bee0b 100644 (file)
@@ -38,24 +38,24 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const SubtitleContent> c)
 void
 SubtitleDecoder::image_subtitle (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
 {
-       _decoded_image_subtitles.push_back (shared_ptr<ContentImageSubtitle> (new ContentImageSubtitle (period, image, rect)));
+       _decoded_image_subtitles.push_back (ContentImageSubtitle (period, image, rect));
 }
 
 void
 SubtitleDecoder::text_subtitle (list<dcp::SubtitleString> s)
 {
-       _decoded_text_subtitles.push_back (shared_ptr<ContentTextSubtitle> (new ContentTextSubtitle (s)));
+       _decoded_text_subtitles.push_back (ContentTextSubtitle (s));
 }
 
 template <class T>
-list<shared_ptr<T> >
-SubtitleDecoder::get (list<shared_ptr<T> > const & subs, ContentTimePeriod period)
+list<T>
+SubtitleDecoder::get (list<T> const & subs, ContentTimePeriod period)
 {
        if (!has_subtitle_during (period)) {
-               return list<shared_ptr<T> > ();
+               return list<T> ();
        }
 
-       if (subs.empty() || period.from < subs.front()->period().from || period.to > (subs.back()->period().to + ContentTime::from_seconds (10))) {
+       if (subs.empty() || period.from < subs.front().period().from || period.to > (subs.back().period().to + ContentTime::from_seconds (10))) {
                /* Either we have no decoded data, or what we do have is a long way from what we want: seek */
                seek (period.from, true);
        }
@@ -64,14 +64,14 @@ SubtitleDecoder::get (list<shared_ptr<T> > const & subs, ContentTimePeriod perio
         *  (a) give us what we want, or
         *  (b) hit the end of the decoder.
         */
-       while (!pass() && (subs.empty() || (subs.front()->period().from > period.from || period.to < subs.back()->period().to))) {}
+       while (!pass() && (subs.empty() || (subs.front().period().from > period.from || period.to < subs.back().period().to))) {}
 
        /* Now look for what we wanted in the data we have collected */
        /* XXX: inefficient */
        
-       list<shared_ptr<T> > out;
-       for (typename list<shared_ptr<T> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
-               if ((*i)->period().overlaps (period)) {
+       list<T> out;
+       for (typename list<T>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               if (i->period().overlaps (period)) {
                        out.push_back (*i);
                }
        }
@@ -79,13 +79,13 @@ SubtitleDecoder::get (list<shared_ptr<T> > const & subs, ContentTimePeriod perio
        return out;
 }
 
-list<shared_ptr<ContentTextSubtitle> >
+list<ContentTextSubtitle>
 SubtitleDecoder::get_text_subtitles (ContentTimePeriod period)
 {
        return get<ContentTextSubtitle> (_decoded_text_subtitles, period);
 }
 
-list<shared_ptr<ContentImageSubtitle> >
+list<ContentImageSubtitle>
 SubtitleDecoder::get_image_subtitles (ContentTimePeriod period)
 {
        return get<ContentImageSubtitle> (_decoded_image_subtitles, period);
index c25edad493a578160af52ec261214cbcaf0653aa..3f06afae396260c1d5d3ebb17fe294905c2dcfc9 100644 (file)
@@ -35,8 +35,8 @@ class SubtitleDecoder : public virtual Decoder
 public:
        SubtitleDecoder (boost::shared_ptr<const SubtitleContent>);
 
-       std::list<boost::shared_ptr<ContentImageSubtitle> > get_image_subtitles (ContentTimePeriod period);
-       std::list<boost::shared_ptr<ContentTextSubtitle> > get_text_subtitles (ContentTimePeriod period);
+       std::list<ContentImageSubtitle> get_image_subtitles (ContentTimePeriod period);
+       std::list<ContentTextSubtitle> get_text_subtitles (ContentTimePeriod period);
 
 protected:
        void seek (ContentTime, bool);
@@ -44,12 +44,12 @@ protected:
        void image_subtitle (ContentTimePeriod period, boost::shared_ptr<Image>, dcpomatic::Rect<double>);
        void text_subtitle (std::list<dcp::SubtitleString>);
 
-       std::list<boost::shared_ptr<ContentImageSubtitle> > _decoded_image_subtitles;
-       std::list<boost::shared_ptr<ContentTextSubtitle> > _decoded_text_subtitles;
+       std::list<ContentImageSubtitle> _decoded_image_subtitles;
+       std::list<ContentTextSubtitle> _decoded_text_subtitles;
 
 private:
        template <class T>
-       std::list<boost::shared_ptr<T> > get (std::list<boost::shared_ptr<T> > const & subs, ContentTimePeriod period);
+       std::list<T> get (std::list<T> const & subs, ContentTimePeriod period);
 
        virtual bool has_subtitle_during (ContentTimePeriod) const = 0;
        
index e4604ccde18584b0a9951c671aaf394e727d359b..e65d9abc0c753703bab9e68dbb7633ebacbe4e93 100644 (file)
@@ -66,15 +66,15 @@ SubtitleView::SubtitleView (wxWindow* parent, shared_ptr<Film> film, shared_ptr<
        }
 
        shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
-       list<shared_ptr<ContentTextSubtitle> > subs = decoder->get_text_subtitles (ContentTimePeriod (ContentTime(), ContentTime::max ()));
+       list<ContentTextSubtitle> subs = decoder->get_text_subtitles (ContentTimePeriod (ContentTime(), ContentTime::max ()));
        FrameRateChange const frc = film->active_frame_rate_change (content->position ());
        int n = 0;
-       for (list<shared_ptr<ContentTextSubtitle> >::const_iterator i = subs.begin(); i != subs.end(); ++i) {
-               for (list<dcp::SubtitleString>::const_iterator j = (*i)->subs.begin(); j != (*i)->subs.end(); ++j) {
+       for (list<ContentTextSubtitle>::const_iterator i = subs.begin(); i != subs.end(); ++i) {
+               for (list<dcp::SubtitleString>::const_iterator j = i->subs.begin(); j != i->subs.end(); ++j) {
                        wxListItem list_item;
                        list_item.SetId (n);
                        _list->InsertItem (list_item);
-                       ContentTimePeriod const p = (*i)->period ();
+                       ContentTimePeriod const p = i->period ();
                        _list->SetItem (n, 0, std_to_wx (p.from.timecode (frc.source)));
                        _list->SetItem (n, 1, std_to_wx (p.to.timecode (frc.source)));
                        _list->SetItem (n, 2, std_to_wx (j->text ()));
index 9479e657e0d03a2bed4f930ac3d10ed2a06fd052..1b207c3c632a730f1440c0cd0b72845a68e99514 100644 (file)
@@ -190,14 +190,14 @@ BOOST_AUTO_TEST_CASE (subrip_render_test)
        BOOST_CHECK_EQUAL (content->full_length(), DCPTime::from_seconds ((3 * 60) + 56.471));
 
        shared_ptr<SubRipDecoder> decoder (new SubRipDecoder (content));
-       list<shared_ptr<ContentTextSubtitle> > cts = decoder->get_text_subtitles (
+       list<ContentTextSubtitle> cts = decoder->get_text_subtitles (
                ContentTimePeriod (
                        ContentTime::from_seconds (109), ContentTime::from_seconds (110)
                        )
                );
        BOOST_CHECK_EQUAL (cts.size(), 1);
 
-       PositionImage image = render_subtitles (cts.front()->subs, dcp::Size (1998, 1080));
+       PositionImage image = render_subtitles (cts.front().subs, dcp::Size (1998, 1080));
        write_image (image.image, "build/test/subrip_render_test.png");
        check_file ("build/test/subrip_render_test.png", "test/data/subrip_render_test.png");
 }