Rather hacky support for burnt subtitle Z-position in 3D (#1359).
authorCarl Hetherington <cth@carlh.net>
Sun, 30 Jan 2022 19:26:58 +0000 (20:26 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 25 Aug 2022 20:11:10 +0000 (22:11 +0200)
src/lib/player.cc
src/lib/player.h
src/lib/player_text.h
src/lib/text_content.cc
src/lib/text_content.h
src/lib/writer.cc
src/wx/film_viewer.cc
src/wx/text_panel.cc
src/wx/text_panel.h

index de9be2b71b43686e8eb0fd261a3eac181b44e329..8013b9d5f5da645a4aad59be2900c0a022734dbd 100644 (file)
@@ -847,18 +847,18 @@ Player::pass ()
 
 /** @return Open subtitles for the frame at the given time, converted to images */
 optional<PositionImage>
-Player::open_subtitles_for_frame (DCPTime time) const
+Player::open_subtitles_for_frame (DCPTime time, Eyes eyes) const
 {
        list<PositionImage> captions;
        int const vfr = _film->video_frame_rate();
 
        for (
-               auto j:
+               auto burnt:
                _active_texts[static_cast<int>(TextType::OPEN_SUBTITLE)].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles)
                ) {
 
                /* Bitmap subtitles */
-               for (auto i: j.bitmap) {
+               for (auto i: burnt.bitmap) {
                        if (!i.image) {
                                continue;
                        }
@@ -878,9 +878,17 @@ Player::open_subtitles_for_frame (DCPTime time) const
                }
 
                /* String subtitles (rendered to an image) */
-               if (!j.string.empty()) {
-                       auto s = render_text(j.string, _video_container_size, time, vfr);
-                       copy (s.begin(), s.end(), back_inserter (captions));
+               if (!burnt.string.empty()) {
+                       int stereo_offset = 0;
+                       if (eyes == Eyes::LEFT) {
+                               stereo_offset = -burnt.z_position;
+                       } else if (eyes == Eyes::RIGHT) {
+                               stereo_offset = burnt.z_position;
+                       }
+                       for (auto subtitle: render_text(burnt.string, _video_container_size, time, vfr)) {
+                               subtitle.position.x += stereo_offset;
+                               captions.push_back (subtitle);
+                       }
                }
        }
 
@@ -1193,6 +1201,7 @@ Player::plain_text_start (weak_ptr<Piece> weak_piece, weak_ptr<const TextContent
                ps.string.push_back (s);
        }
 
+       ps.z_position = text->z_position();
        _active_texts[static_cast<int>(content->type())].add_from(weak_content, ps, from);
 }
 
@@ -1324,7 +1333,19 @@ Player::emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
 
        auto to_do = _delay.front();
        _delay.pop_front();
-       do_emit_video (to_do.first, to_do.second);
+       if (_film->three_d() && to_do.first->eyes() == Eyes::BOTH) {
+               /* Duplicate BOTH to LEFT and RIGHT here so that we can to 3D
+                * subtitles over 2D content.
+                */
+               auto left = to_do.first->shallow_copy();
+               left->set_eyes (Eyes::LEFT);
+               do_emit_video (left, to_do.second);
+               auto right = to_do.first->shallow_copy();
+               right->set_eyes (Eyes::RIGHT);
+               do_emit_video (right, to_do.second);
+       } else {
+               do_emit_video (to_do.first, to_do.second);
+       }
 }
 
 
@@ -1337,7 +1358,7 @@ Player::do_emit_video (shared_ptr<PlayerVideo> pv, DCPTime time)
                }
        }
 
-       auto subtitles = open_subtitles_for_frame (time);
+       auto subtitles = open_subtitles_for_frame (time, pv->eyes());
        if (subtitles) {
                pv->set_text (subtitles.get ());
        }
index 694ee70b77cca871eb83854a2b3a8c0a964da1f7..749fd3261852270cc4f83ce9638298e15c949abe 100644 (file)
@@ -151,7 +151,7 @@ private:
        std::pair<std::shared_ptr<AudioBuffers>, dcpomatic::DCPTime> discard_audio (
                std::shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, dcpomatic::DCPTime discard_to
                ) const;
-       boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time) const;
+       boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time, Eyes eyes) const;
        void emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
        void do_emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
        void emit_audio (std::shared_ptr<AudioBuffers> data, dcpomatic::DCPTime time);
index 38affd5ef1cc0953e8bee3ad87b1df8dcc8fc94f..20b7cb3bdb479aadfaa6350e9ee962217f9852ea 100644 (file)
@@ -39,6 +39,9 @@ public:
        /** BitmapTexts, with their rectangles transformed as specified by their content */
        std::list<BitmapText> bitmap;
        std::list<StringText> string;
+
+       /* XXX: this should probably be somewhere else */
+       int z_position = 0;
 };
 
 
index a85b271a88d23dbfca216491e5777e701c720f03..0f5bc60bec6827656e0610eaa55d51451c4a30bb 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2022 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 */
 
 
-#include "text_content.h"
-#include "util.h"
+#include "content.h"
 #include "exceptions.h"
 #include "font.h"
-#include "content.h"
+#include "text_content.h"
+#include "util.h"
 #include <dcp/raw_convert.h>
 #include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
@@ -55,13 +55,14 @@ int const TextContentProperty::COLOUR = 507;
 int const TextContentProperty::EFFECT = 508;
 int const TextContentProperty::EFFECT_COLOUR = 509;
 int const TextContentProperty::LINE_SPACING = 510;
-int const TextContentProperty::FADE_IN = 511;
-int const TextContentProperty::FADE_OUT = 512;
-int const TextContentProperty::OUTLINE_WIDTH = 513;
-int const TextContentProperty::TYPE = 514;
-int const TextContentProperty::DCP_TRACK = 515;
-int const TextContentProperty::LANGUAGE = 516;
-int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 517;
+int const TextContentProperty::Z_POSITION = 511;
+int const TextContentProperty::FADE_IN = 512;
+int const TextContentProperty::FADE_OUT = 513;
+int const TextContentProperty::OUTLINE_WIDTH = 514;
+int const TextContentProperty::TYPE = 515;
+int const TextContentProperty::DCP_TRACK = 516;
+int const TextContentProperty::LANGUAGE = 517;
+int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 518;
 
 
 TextContent::TextContent (Content* parent, TextType type, TextType original_type)
@@ -121,6 +122,7 @@ TextContent::TextContent (Content* parent, cxml::ConstNodePtr node, int version,
        , _x_scale (1)
        , _y_scale (1)
        , _line_spacing (node->optional_number_child<double>("LineSpacing").get_value_or(1))
+       , _z_position (node->optional_number_child<int>("ZPosition").get_value_or(0))
        , _outline_width (node->optional_number_child<int>("OutlineWidth").get_value_or(4))
        , _type (TextType::OPEN_SUBTITLE)
        , _original_type (TextType::OPEN_SUBTITLE)
@@ -295,6 +297,10 @@ TextContent::TextContent (Content* parent, vector<shared_ptr<Content>> c)
                        throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
                }
 
+               if (c[i]->only_text()->z_position() != ref->z_position()) {
+                       throw JoinError (_("Content to be joined must have the same subtitle Z position."));
+               }
+
                if ((c[i]->only_text()->fade_in() != ref->fade_in()) || (c[i]->only_text()->fade_out() != ref->fade_out())) {
                        throw JoinError (_("Content to be joined must have the same subtitle fades."));
                }
@@ -340,6 +346,7 @@ TextContent::TextContent (Content* parent, vector<shared_ptr<Content>> c)
        _y_scale = ref->y_scale ();
        _fonts = ref_fonts;
        _line_spacing = ref->line_spacing ();
+       _z_position = ref->z_position ();
        _fade_in = ref->fade_in ();
        _fade_out = ref->fade_out ();
        _outline_width = ref->outline_width ();
@@ -390,6 +397,7 @@ TextContent::as_xml (xmlpp::Node* root) const
                text->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour->b));
        }
        text->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+       text->add_child("ZPosition")->add_child_text(raw_convert<string>(_z_position));
        if (_fade_in) {
                text->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in->get()));
        }
@@ -422,6 +430,7 @@ TextContent::identifier () const
                + "_" + raw_convert<string> (x_offset())
                + "_" + raw_convert<string> (y_offset())
                + "_" + raw_convert<string> (line_spacing())
+               + "_" + raw_convert<string> (z_position())
                + "_" + raw_convert<string> (fade_in().get_value_or(ContentTime()).get())
                + "_" + raw_convert<string> (fade_out().get_value_or(ContentTime()).get())
                + "_" + raw_convert<string> (outline_width())
@@ -551,6 +560,13 @@ TextContent::set_line_spacing (double s)
        maybe_set (_line_spacing, s, TextContentProperty::LINE_SPACING);
 }
 
+void
+TextContent::set_z_position (int z)
+{
+       maybe_set (_z_position, z, TextContentProperty::Z_POSITION);
+}
+
+
 void
 TextContent::set_fade_in (ContentTime t)
 {
@@ -635,6 +651,7 @@ TextContent::take_settings_from (shared_ptr<const TextContent> c)
                unset_effect_colour ();
        }
        set_line_spacing (c->_line_spacing);
+       set_z_position (c->_z_position);
        if (c->_fade_in) {
                set_fade_in (*c->_fade_in);
        }
index 7c060cd482afb76fa3bdd152bb8dd178c4d2350f..bfa33e8dfe53d1d8a7f53d0dec61b55455ba40ee 100644 (file)
@@ -49,6 +49,7 @@ public:
        static int const EFFECT;
        static int const EFFECT_COLOUR;
        static int const LINE_SPACING;
+       static int const Z_POSITION;
        static int const FADE_IN;
        static int const FADE_OUT;
        static int const OUTLINE_WIDTH;
@@ -93,6 +94,7 @@ public:
        void set_effect_colour (dcp::Colour);
        void unset_effect_colour ();
        void set_line_spacing (double s);
+       void set_z_position (int z);
        void set_fade_in (dcpomatic::ContentTime);
        void unset_fade_in ();
        void set_fade_out (dcpomatic::ContentTime);
@@ -159,6 +161,11 @@ public:
                return _line_spacing;
        }
 
+       int z_position () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _z_position;
+       }
+
        boost::optional<dcpomatic::ContentTime> fade_in () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _fade_in;
@@ -230,6 +237,7 @@ private:
        boost::optional<dcp::Colour> _effect_colour;
        /** scaling factor for line spacing; 1 is "standard", < 1 is closer together, > 1 is further apart */
        double _line_spacing;
+       int _z_position = 0;
        boost::optional<dcpomatic::ContentTime> _fade_in;
        boost::optional<dcpomatic::ContentTime> _fade_out;
        int _outline_width;
index 7792dda8e28c7655d7c1c372a3ab7116bda0980e..a760185ecf3611d8718df718d309d00046114ab3 100644 (file)
@@ -154,7 +154,9 @@ Writer::write (shared_ptr<const Data> encoded, Frame frame, Eyes eyes)
        qi.frame = frame - _reels[qi.reel].start ();
 
        if (film()->three_d() && eyes == Eyes::BOTH) {
-               /* 2D material in a 3D DCP; fake the 3D */
+               /* 2D material in a 3D DCP; fake the 3D.
+                * XXX: this shouldn't really be necessary any more but something is still sending BOTH
+                */
                qi.eyes = Eyes::LEFT;
                _queue.push_back (qi);
                ++_queued_full_in_memory;
index b5b2ca972e57aa345856cea8ce28037db1788283..11359ad9af7b4a1127c38a518b1659bd91cbec16 100644 (file)
@@ -469,6 +469,7 @@ FilmViewer::film_change (ChangeType type, Film::Property p)
                _video_view->set_video_frame_rate (_film->video_frame_rate());
        } else if (p == Film::Property::THREE_D) {
                _video_view->set_three_d (_film->three_d());
+               slow_refresh ();
        } else if (p == Film::Property::CONTENT) {
                _closed_captions_dialog->update_tracks (_film);
        }
index 42dace9ba81e817847ad159a8515507ab425a3a4..980d8fd279a59cc988b56f68fb7c5b3e4ed8690f 100644 (file)
@@ -113,6 +113,9 @@ TextPanel::create ()
        _line_spacing = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
        _line_spacing_pc_label = new StaticText (this, _("%"));
 
+       _z_position_label = create_label (this, _("Z position"), true);
+       _z_position = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
+
        _stream_label = create_label (this, _("Stream"), true);
        _stream = new wxChoice (this, wxID_ANY);
 
@@ -135,6 +138,7 @@ TextPanel::create ()
        _x_scale->Bind                  (wxEVT_SPINCTRL, boost::bind (&TextPanel::x_scale_changed, this));
        _y_scale->Bind                  (wxEVT_SPINCTRL, boost::bind (&TextPanel::y_scale_changed, this));
        _line_spacing->Bind             (wxEVT_SPINCTRL, boost::bind (&TextPanel::line_spacing_changed, this));
+       _z_position->Bind               (wxEVT_SPINCTRL, boost::bind (&TextPanel::z_position_changed, this));
        _stream->Bind                   (wxEVT_CHOICE,   boost::bind (&TextPanel::stream_changed, this));
        _text_view_button->Bind         (wxEVT_BUTTON,   boost::bind (&TextPanel::text_view_clicked, this));
        _fonts_dialog_button->Bind      (wxEVT_BUTTON,   boost::bind (&TextPanel::fonts_dialog_clicked, this));
@@ -285,6 +289,10 @@ TextPanel::add_to_grid ()
                ++r;
        }
 
+       add_label_to_sizer (_grid, _z_position_label, true, wxGBPosition(r, 0));
+       _grid->Add (_z_position, wxGBPosition(r, 1));
+       ++r;
+
        _ccap_track_or_language_row = r;
        ++r;
 
@@ -479,6 +487,8 @@ TextPanel::film_content_changed (int property)
        } else if (property == TextContentProperty::LINE_SPACING) {
                checked_set (_line_spacing, text ? lrint (text->line_spacing() * 100) : 100);
                clear_outline_subtitles ();
+       } else if (property == TextContentProperty::Z_POSITION) {
+               checked_set (_z_position, text ? text->z_position() : 0);
        } else if (property == TextContentProperty::DCP_TRACK) {
                if (_dcp_track) {
                        update_dcp_track_selection ();
@@ -635,6 +645,7 @@ TextPanel::setup_sensitivity ()
        _x_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
        _y_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
        _line_spacing->Enable (!reference && use && type == TextType::OPEN_SUBTITLE && dcp_subs < any_subs);
+       _z_position->Enable (!reference && use && type == TextType::OPEN_SUBTITLE);
        _stream->Enable (!reference && ffmpeg_subs == 1);
        /* Ideally we would check here to see if the FFmpeg content has "string" subs (i.e. not bitmaps) */
        _text_view_button->Enable (!reference && any_subs > 0 && ffmpeg_subs == 0);
@@ -711,6 +722,15 @@ TextPanel::line_spacing_changed ()
 }
 
 
+void
+TextPanel::z_position_changed ()
+{
+       for (auto i: _parent->selected_text()) {
+               i->text_of_original_type(_original_type)->set_z_position (_z_position->GetValue());
+       }
+}
+
+
 void
 TextPanel::content_selection_changed ()
 {
@@ -722,6 +742,7 @@ TextPanel::content_selection_changed ()
        film_content_changed (TextContentProperty::X_SCALE);
        film_content_changed (TextContentProperty::Y_SCALE);
        film_content_changed (TextContentProperty::LINE_SPACING);
+       film_content_changed (TextContentProperty::Z_POSITION);
        film_content_changed (TextContentProperty::FONTS);
        film_content_changed (TextContentProperty::TYPE);
        film_content_changed (TextContentProperty::DCP_TRACK);
index c4498f970b7a8d6c6e3d01911d44ca1fe4e1fe34..129b5d369cc1355a4dad255438994b8b74f772e7 100644 (file)
@@ -50,6 +50,7 @@ private:
        void x_scale_changed ();
        void y_scale_changed ();
        void line_spacing_changed ();
+       void z_position_changed ();
        void dcp_track_changed ();
        void stream_changed ();
        void text_view_clicked ();
@@ -95,6 +96,8 @@ private:
        wxStaticText* _line_spacing_label;
        wxStaticText* _line_spacing_pc_label;
        SpinCtrl* _line_spacing;
+       wxStaticText* _z_position_label;
+       SpinCtrl* _z_position;
        wxStaticText* _dcp_track_label = nullptr;
        wxChoice* _dcp_track = nullptr;
        wxStaticText* _stream_label;