Basic support for fading subtitles in and out (#923).
authorCarl Hetherington <cth@carlh.net>
Tue, 16 Aug 2016 07:41:25 +0000 (08:41 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 16 Aug 2016 15:56:32 +0000 (16:56 +0100)
ChangeLog
src/lib/player.cc
src/lib/render_subtitles.cc
src/lib/render_subtitles.h
src/lib/subtitle_content.cc
src/lib/subtitle_content.h
src/lib/subtitle_decoder.cc
src/wx/subtitle_panel.cc
src/wx/text_subtitle_appearance_dialog.cc
src/wx/text_subtitle_appearance_dialog.h

index c6d270897aa91a3c7d25c94050893851e36a8b37..60e3de29c05a427e73896905d4b55fef2ed20944 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,7 @@
 2016-08-16  c.hetherington  <cth@carlh.net>
 
+       * Basic support for fading subtitles in and out (#923).
+
        * Fix error on seeking through imported mulit-reel DCPs.
 
        * Simple information on mouse position in the video waveform (part of #932).
index 7dcaf68b745267be818b790009bd8116e95a923e..07bb097c2632a95df233497c7dd5a42aa1783c13 100644 (file)
@@ -337,7 +337,7 @@ Player::get_video (DCPTime time, bool accurate)
 
        /* Text subtitles (rendered to an image) */
        if (!ps.text.empty ()) {
-               list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
+               list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size, time);
                copy (s.begin (), s.end (), back_inserter (sub_images));
        }
 
index 5c8cf13eedc32e98d68c059ead8f3be083b2a616..6e4dcf46ad4958d899eb55eccaacf8690041a06b 100644 (file)
@@ -92,9 +92,11 @@ marked_up (list<dcp::SubtitleString> subtitles)
        return out;
 }
 
-/** @param subtitles A list of subtitles that are all on the same line */
+/** @param subtitles A list of subtitles that are all on the same line,
+ *  at the same time and with the same fade in/out.
+ */
 static PositionImage
-render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target)
+render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time)
 {
        /* XXX: this method can only handle italic / bold changes mid-line,
           nothing else yet.
@@ -205,7 +207,9 @@ render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts,
                        }
                }
 
-               FcPattern* pattern = FcPatternBuild (0, FC_FILE, FcTypeString, font_files.get(FontFiles::NORMAL).get().string().c_str(), static_cast<char *> (0));
+               FcPattern* pattern = FcPatternBuild (
+                       0, FC_FILE, FcTypeString, font_files.get(FontFiles::NORMAL).get().string().c_str(), static_cast<char *> (0)
+                       );
                FcObjectSet* object_set = FcObjectSetBuild (FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, static_cast<char *> (0));
                FcFontSet* font_set = FcFontList (fc_config, pattern, object_set);
                if (font_set) {
@@ -248,9 +252,18 @@ render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts,
        layout->set_markup (marked_up (subtitles));
 
        /* Compute fade factor */
-       /* XXX */
        float fade_factor = 1;
 
+       DCPTime const fade_in_start = DCPTime::from_seconds (subtitles.front().in().as_seconds ());
+       DCPTime const fade_in_end = fade_in_start + DCPTime::from_seconds (subtitles.front().fade_up_time().as_seconds ());
+       DCPTime const fade_out_end =  DCPTime::from_seconds (subtitles.front().out().as_seconds ());
+       DCPTime const fade_out_start = fade_out_end - DCPTime::from_seconds (subtitles.front().fade_down_time().as_seconds ());
+       if (fade_in_start <= time && time <= fade_in_end && fade_in_start != fade_in_end) {
+               fade_factor = DCPTime(time - fade_in_start).seconds() / DCPTime(fade_in_end - fade_in_start).seconds();
+       } else if (fade_out_start <= time && time <= fade_out_end && fade_out_start != fade_out_end) {
+               fade_factor = 1 - DCPTime(time - fade_out_start).seconds() / DCPTime(fade_out_end - fade_out_start).seconds();
+       }
+
        context->scale (xscale, yscale);
        layout->update_from_cairo_context (context);
 
@@ -335,22 +348,23 @@ render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts,
        return PositionImage (image, Position<int> (max (0, x), max (0, y)));
 }
 
+/** @param time Time of the frame that these subtitles are going on */
 list<PositionImage>
-render_subtitles (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target)
+render_subtitles (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time)
 {
        list<dcp::SubtitleString> pending;
        list<PositionImage> images;
 
        BOOST_FOREACH (dcp::SubtitleString const & i, subtitles) {
                if (!pending.empty() && fabs (i.v_position() - pending.back().v_position()) > 1e-4) {
-                       images.push_back (render_line (pending, fonts, target));
+                       images.push_back (render_line (pending, fonts, target, time));
                        pending.clear ();
                }
                pending.push_back (i);
        }
 
        if (!pending.empty ()) {
-               images.push_back (render_line (pending, fonts, target));
+               images.push_back (render_line (pending, fonts, target, time));
        }
 
        return images;
index 281efe97afcb3649cf01451a91542c33d62179ce..79194568a9211511da7a675ba5404758d47b073a 100644 (file)
 */
 
 #include "position_image.h"
+#include "dcpomatic_time.h"
 #include <dcp/subtitle_string.h>
 #include <dcp/util.h>
 
 class Font;
 
 std::string marked_up (std::list<dcp::SubtitleString> subtitles);
-std::list<PositionImage> render_subtitles (std::list<dcp::SubtitleString>, std::list<boost::shared_ptr<Font> > fonts, dcp::Size);
+std::list<PositionImage> render_subtitles (
+       std::list<dcp::SubtitleString>, std::list<boost::shared_ptr<Font> > fonts, dcp::Size, DCPTime
+       );
index 21bfd8a2d5fbe18ee7ab0f83c66c3d9ef368cb20..c57c72fb1996560d0dd2aa5d3f0b8bcd8e6cb7f4 100644 (file)
@@ -52,6 +52,8 @@ int const SubtitleContentProperty::OUTLINE = 509;
 int const SubtitleContentProperty::SHADOW = 510;
 int const SubtitleContentProperty::EFFECT_COLOUR = 511;
 int const SubtitleContentProperty::LINE_SPACING = 512;
+int const SubtitleContentProperty::FADE_IN = 513;
+int const SubtitleContentProperty::FADE_OUT = 514;
 
 SubtitleContent::SubtitleContent (Content* parent)
        : ContentPart (parent)
@@ -107,6 +109,8 @@ SubtitleContent::SubtitleContent (Content* parent, cxml::ConstNodePtr node, int
        , _outline (node->optional_bool_child("Outline").get_value_or(false))
        , _shadow (node->optional_bool_child("Shadow").get_value_or(false))
        , _line_spacing (node->optional_number_child<double>("LineSpacing").get_value_or (1))
+       , _fade_in (node->optional_number_child<Frame>("SubtitleFadeIn").get_value_or (0))
+       , _fade_out (node->optional_number_child<Frame>("SubtitleFadeOut").get_value_or (0))
 {
        if (version >= 32) {
                _use = node->bool_child ("UseSubtitles");
@@ -188,6 +192,10 @@ SubtitleContent::SubtitleContent (Content* parent, vector<shared_ptr<Content> >
                        throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
                }
 
+               if ((c[i]->subtitle->fade_in() != ref->fade_in()) || (c[i]->subtitle->fade_out() != ref->fade_out())) {
+                       throw JoinError (_("Content to be joined must have the same subtitle fades."));
+               }
+
                list<shared_ptr<Font> > fonts = c[i]->subtitle->fonts ();
                if (fonts.size() != ref_fonts.size()) {
                        throw JoinError (_("Content to be joined must use the same fonts."));
@@ -214,6 +222,8 @@ SubtitleContent::SubtitleContent (Content* parent, vector<shared_ptr<Content> >
        _language = ref->language ();
        _fonts = ref_fonts;
        _line_spacing = ref->line_spacing ();
+       _fade_in = ref->fade_in ();
+       _fade_out = ref->fade_out ();
 
        connect_to_fonts ();
 }
@@ -240,6 +250,8 @@ SubtitleContent::as_xml (xmlpp::Node* root) const
        root->add_child("EffectGreen")->add_child_text (raw_convert<string> (_effect_colour.g));
        root->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour.b));
        root->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+       root->add_child("SubtitleFadeIn")->add_child_text (raw_convert<string> (_fade_in.get()));
+       root->add_child("SubtitleFadeOut")->add_child_text (raw_convert<string> (_fade_out.get()));
 
        for (list<shared_ptr<Font> >::const_iterator i = _fonts.begin(); i != _fonts.end(); ++i) {
                (*i)->as_xml (root->add_child("Font"));
@@ -253,7 +265,9 @@ SubtitleContent::identifier () const
                + "_" + raw_convert<string> (y_scale())
                + "_" + raw_convert<string> (x_offset())
                + "_" + raw_convert<string> (y_offset())
-               + "_" + raw_convert<string> (line_spacing());
+               + "_" + raw_convert<string> (line_spacing())
+               + "_" + raw_convert<string> (fade_in().get())
+               + "_" + raw_convert<string> (fade_out().get());
 
        /* XXX: I suppose really _fonts shouldn't be in here, since not all
           types of subtitle content involve fonts.
@@ -369,3 +383,15 @@ SubtitleContent::set_line_spacing (double s)
 {
        maybe_set (_line_spacing, s, SubtitleContentProperty::LINE_SPACING);
 }
+
+void
+SubtitleContent::set_fade_in (ContentTime t)
+{
+       maybe_set (_fade_in, t, SubtitleContentProperty::FADE_IN);
+}
+
+void
+SubtitleContent::set_fade_out (ContentTime t)
+{
+       maybe_set (_fade_out, t, SubtitleContentProperty::FADE_OUT);
+}
index c8492b91a32359c3eea8262cb00a3e32d516ba90..fd02d032ae79f384463210a65d036034411b496d 100644 (file)
@@ -44,6 +44,8 @@ public:
        static int const SHADOW;
        static int const EFFECT_COLOUR;
        static int const LINE_SPACING;
+       static int const FADE_IN;
+       static int const FADE_OUT;
 };
 
 /** @class SubtitleContent
@@ -70,6 +72,13 @@ public:
        void set_x_scale (double);
        void set_y_scale (double);
        void set_language (std::string language);
+       void set_colour (dcp::Colour);
+       void set_outline (bool);
+       void set_shadow (bool);
+       void set_effect_colour (dcp::Colour);
+       void set_line_spacing (double s);
+       void set_fade_in (ContentTime);
+       void set_fade_out (ContentTime);
 
        bool use () const {
                boost::mutex::scoped_lock lm (_mutex);
@@ -111,41 +120,41 @@ public:
                return _language;
        }
 
-       void set_colour (dcp::Colour);
-
        dcp::Colour colour () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _colour;
        }
 
-       void set_outline (bool);
-
        bool outline () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _outline;
        }
 
-       void set_shadow (bool);
-
        bool shadow () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _shadow;
        }
 
-       void set_effect_colour (dcp::Colour);
-
        dcp::Colour effect_colour () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _effect_colour;
        }
 
-       void set_line_spacing (double s);
-
        double line_spacing () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _line_spacing;
        }
 
+       ContentTime fade_in () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _fade_in;
+       }
+
+       ContentTime fade_out () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _fade_out;
+       }
+
        static boost::shared_ptr<SubtitleContent> from_xml (Content* parent, cxml::ConstNodePtr, int version);
 
 protected:
@@ -159,6 +168,8 @@ private:
        void font_changed ();
        void connect_to_fonts ();
 
+       std::list<boost::signals2::connection> _font_connections;
+
        bool _use;
        bool _burn;
        /** x offset for placing subtitles, as a proportion of the container width;
@@ -178,9 +189,10 @@ private:
        bool _outline;
        bool _shadow;
        dcp::Colour _effect_colour;
-       std::list<boost::signals2::connection> _font_connections;
        /** scaling factor for line spacing; 1 is "standard", < 1 is closer together, > 1 is further apart */
        double _line_spacing;
+       ContentTime _fade_in;
+       ContentTime _fade_out;
 };
 
 #endif
index 5ae1a703eb22e40a28b55dd0aff655bec4782755..ba6fe460034c2f38dd0df54cf4314349dcfb369b 100644 (file)
@@ -245,8 +245,8 @@ SubtitleDecoder::give_text (ContentTimePeriod period, sub::Subtitle const & subt
                                        j.text,
                                        effect,
                                        content()->effect_colour(),
-                                       dcp::Time (0, 1000),
-                                       dcp::Time (0, 1000)
+                                       dcp::Time (content()->fade_in().seconds(), 1000),
+                                       dcp::Time (content()->fade_out().seconds(), 1000)
                                        )
                                );
                }
index 3db50104f426c420c415ad3533fcbaaa0bf1137e..e4db3560e95cb98ef6c9f9c415e747fb43c48833 100644 (file)
@@ -477,7 +477,7 @@ SubtitlePanel::appearance_dialog_clicked ()
        }
 
        if (text) {
-               TextSubtitleAppearanceDialog* d = new TextSubtitleAppearanceDialog (this, c.front()->subtitle);
+               TextSubtitleAppearanceDialog* d = new TextSubtitleAppearanceDialog (this, c.front());
                if (d->ShowModal () == wxID_OK) {
                        d->apply ();
                }
index a826cbc6689f0705ab2d932595726bfa5811444e..49c8920f6d820507e3d93829e52b11ad37675f72 100644 (file)
@@ -26,7 +26,7 @@
 
 using boost::shared_ptr;
 
-TextSubtitleAppearanceDialog::TextSubtitleAppearanceDialog (wxWindow* parent, shared_ptr<SubtitleContent> content)
+TextSubtitleAppearanceDialog::TextSubtitleAppearanceDialog (wxWindow* parent, shared_ptr<Content> content)
        : TableDialog (parent, _("Subtitle appearance"), 2, 1, true)
        , _content (content)
 {
@@ -50,23 +50,35 @@ TextSubtitleAppearanceDialog::TextSubtitleAppearanceDialog (wxWindow* parent, sh
        _effect_colour = new wxColourPickerCtrl (this, wxID_ANY);
        add (_effect_colour);
 
+       add (_("Fade in time"), true);
+       _fade_in = new Timecode<ContentTime> (this);
+       add (_fade_in);
+
+       add (_("Fade out time"), true);
+       _fade_out = new Timecode<ContentTime> (this);
+       add (_fade_out);
+
        layout ();
 
-       _colour->SetColour (wxColour (_content->colour().r, _content->colour().g, _content->colour().b));
-       _outline->SetValue (_content->outline ());
-       _shadow->SetValue (_content->shadow ());
+       _colour->SetColour (wxColour (_content->subtitle->colour().r, _content->subtitle->colour().g, _content->subtitle->colour().b));
+       _outline->SetValue (_content->subtitle->outline ());
+       _shadow->SetValue (_content->subtitle->shadow ());
        _effect_colour->SetColour (
-               wxColour (_content->effect_colour().r, _content->effect_colour().g, _content->effect_colour().b)
+               wxColour (_content->subtitle->effect_colour().r, _content->subtitle->effect_colour().g, _content->subtitle->effect_colour().b)
                );
+       _fade_in->set (_content->subtitle->fade_in(), _content->active_video_frame_rate ());
+       _fade_out->set (_content->subtitle->fade_out(), _content->active_video_frame_rate ());
 }
 
 void
 TextSubtitleAppearanceDialog::apply ()
 {
        wxColour const c = _colour->GetColour ();
-       _content->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
-       _content->set_outline (_outline->GetValue ());
-       _content->set_shadow (_shadow->GetValue ());
+       _content->subtitle->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
+       _content->subtitle->set_outline (_outline->GetValue ());
+       _content->subtitle->set_shadow (_shadow->GetValue ());
        wxColour const ec = _effect_colour->GetColour ();
-       _content->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
+       _content->subtitle->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
+       _content->subtitle->set_fade_in (_fade_in->get (_content->active_video_frame_rate ()));
+       _content->subtitle->set_fade_out (_fade_out->get (_content->active_video_frame_rate ()));
 }
index c67ec8756bafe0d1d55fe8b3154e1d962301a135..55fe51066b325d03a856fc4c8664aa8ef0589d4f 100644 (file)
 */
 
 #include "table_dialog.h"
+#include "timecode.h"
 #include <boost/shared_ptr.hpp>
 
 class wxRadioButton;
 class wxColourPickerCtrl;
-class SubtitleContent;
+class Content;
 
 class TextSubtitleAppearanceDialog : public TableDialog
 {
 public:
-       TextSubtitleAppearanceDialog (wxWindow* parent, boost::shared_ptr<SubtitleContent> content);
+       TextSubtitleAppearanceDialog (wxWindow* parent, boost::shared_ptr<Content> content);
 
        void apply ();
 
@@ -37,6 +38,8 @@ private:
        wxRadioButton* _outline;
        wxRadioButton* _shadow;
        wxColourPickerCtrl* _effect_colour;
+       Timecode<ContentTime>* _fade_in;
+       Timecode<ContentTime>* _fade_out;
 
-       boost::shared_ptr<SubtitleContent> _content;
+       boost::shared_ptr<Content> _content;
 };