diff options
| author | Carl Hetherington <cth@carlh.net> | 2023-11-15 20:14:55 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2023-11-15 20:14:55 +0100 |
| commit | e3fa86ef35f212b14b593dd36dbff66e845d37e4 (patch) | |
| tree | c5d6ed58fc19b0fcd08c02a67df66d7023337728 /src | |
| parent | 24ba38ed1d695a67aebc8a6084444345787112f9 (diff) | |
Simple pass-through of <Ruby> tags in subtitles.
Diffstat (limited to 'src')
| -rw-r--r-- | src/ruby.cc | 57 | ||||
| -rw-r--r-- | src/ruby.h | 71 | ||||
| -rw-r--r-- | src/subtitle_asset.cc | 97 | ||||
| -rw-r--r-- | src/subtitle_asset.h | 8 | ||||
| -rw-r--r-- | src/subtitle_asset_internal.cc | 12 | ||||
| -rw-r--r-- | src/subtitle_asset_internal.h | 5 | ||||
| -rw-r--r-- | src/subtitle_string.cc | 17 | ||||
| -rw-r--r-- | src/subtitle_string.h | 14 | ||||
| -rw-r--r-- | src/types.h | 5 | ||||
| -rw-r--r-- | src/wscript | 2 |
10 files changed, 278 insertions, 10 deletions
diff --git a/src/ruby.cc b/src/ruby.cc new file mode 100644 index 00000000..6adfbcbc --- /dev/null +++ b/src/ruby.cc @@ -0,0 +1,57 @@ +/* + Copyright (C) 2023 Carl Hetherington <cth@carlh.net> + + This file is part of libdcp. + + libdcp 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. + + libdcp 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 libdcp. If not, see <http://www.gnu.org/licenses/>. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include "ruby.h" +#include "types.h" + + +using namespace dcp; + + +bool dcp::operator==(Ruby const& a, Ruby const& b) +{ + return a.base == b.base && + a.annotation == b.annotation && + fabs(a.size - b.size) < SIZE_EPSILON && + a. position == b.position && + fabs(a.offset - b.offset) < OFFSET_EPSILON && + fabs(a.spacing - b.spacing) < SPACING_EPSILON && + fabs(a.aspect_adjust - b.aspect_adjust) < ASPECT_ADJUST_EPSILON; +} + + +bool dcp::operator!=(Ruby const& a, Ruby const& b) +{ + return !(a == b); +} diff --git a/src/ruby.h b/src/ruby.h new file mode 100644 index 00000000..343583dd --- /dev/null +++ b/src/ruby.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 2023 Carl Hetherington <cth@carlh.net> + + This file is part of libdcp. + + libdcp 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. + + libdcp 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 libdcp. If not, see <http://www.gnu.org/licenses/>. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include <string> + + +namespace dcp { + + +enum class RubyPosition +{ + BEFORE, + AFTER +}; + + +class Ruby +{ +public: + Ruby(std::string base_, std::string annotation_) + : base(base_) + , annotation(annotation_) + {} + + std::string base; + std::string annotation; + float size = 0.5; + RubyPosition position = RubyPosition::BEFORE; + float offset = 0; + float spacing = 0; + float aspect_adjust = 1; +}; + + +bool operator==(Ruby const& a, Ruby const& b); +bool operator!=(Ruby const& a, Ruby const& b); + + +} + diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc index 4deb366a..1cd4fc07 100644 --- a/src/subtitle_asset.cc +++ b/src/subtitle_asset.cc @@ -294,12 +294,83 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>& float space_before = 0; + /* Collect <Ruby>s first */ + auto get_text_content = [](xmlpp::Element const* element) { + string all_content; + for (auto child: element->get_children()) { + auto content = dynamic_cast<xmlpp::ContentNode const*>(child); + if (content) { + all_content += content->get_content(); + } + } + return all_content; + }; + + vector<Ruby> rubies; + for (auto child: node->get_children()) { + auto element = dynamic_cast<xmlpp::Element const*>(child); + if (element && element->get_name() == "Ruby") { + optional<string> base; + optional<string> annotation; + optional<float> size; + optional<RubyPosition> position; + optional<float> offset; + optional<float> spacing; + optional<float> aspect_adjust; + for (auto ruby_child: element->get_children()) { + if (auto ruby_element = dynamic_cast<xmlpp::Element const*>(ruby_child)) { + if (ruby_element->get_name() == "Rb") { + base = get_text_content(ruby_element); + } else if (ruby_element->get_name() == "Rt") { + annotation = get_text_content(ruby_element); + size = optional_number_attribute<float>(ruby_element, "Size"); + if (auto position_string = optional_string_attribute(ruby_element, "Position")) { + if (*position_string == "before") { + position = RubyPosition::BEFORE; + } else if (*position_string == "after") { + position = RubyPosition::AFTER; + } else { + DCP_ASSERT(false); + } + } + offset = optional_number_attribute<float>(ruby_element, "Offset"); + spacing = optional_number_attribute<float>(ruby_element, "Spacing"); + aspect_adjust = optional_number_attribute<float>(ruby_element, "AspectAdjust"); + } + } + } + DCP_ASSERT(base); + DCP_ASSERT(annotation); + auto ruby = Ruby{*base, *annotation}; + if (size) { + ruby.size = *size; + } + if (position) { + ruby.position = *position; + } + if (offset) { + ruby.offset = *offset; + } + if (spacing) { + ruby.spacing = *spacing; + } + if (aspect_adjust) { + ruby.aspect_adjust = *aspect_adjust; + } + rubies.push_back(ruby); + } + } + for (auto i: node->get_children()) { + + /* Handle actual content e.g. text */ auto const v = dynamic_cast<xmlpp::ContentNode const *>(i); if (v) { - maybe_add_subtitle (v->get_content(), state, space_before, standard); + maybe_add_subtitle (v->get_content(), state, space_before, standard, rubies); space_before = 0; } + + /* Handle other nodes */ auto const e = dynamic_cast<xmlpp::Element const *>(i); if (e) { if (e->get_name() == "Space") { @@ -311,7 +382,7 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>& boost::replace_all(size, "em", ""); } space_before += raw_convert<float>(size); - } else { + } else if (e->get_name() != "Ruby") { parse_subtitles (e, state, tcr, standard); } } @@ -322,7 +393,13 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>& void -SubtitleAsset::maybe_add_subtitle (string text, vector<ParseState> const & parse_state, float space_before, Standard standard) +SubtitleAsset::maybe_add_subtitle( + string text, + vector<ParseState> const & parse_state, + float space_before, + Standard standard, + vector<Ruby> const& rubies + ) { auto wanted = [](ParseState const& ps) { return ps.type && (ps.type.get() == ParseState::Type::TEXT || ps.type.get() == ParseState::Type::IMAGE); @@ -427,7 +504,8 @@ SubtitleAsset::maybe_add_subtitle (string text, vector<ParseState> const & parse ps.effect_colour.get_value_or (dcp::Colour (0, 0, 0)), ps.fade_up_time.get_value_or(Time()), ps.fade_down_time.get_value_or(Time()), - space_before + space_before, + rubies ) ); break; @@ -698,7 +776,16 @@ SubtitleAsset::subtitles_as_xml (xmlpp::Element* xml_root, int time_code_rate, S fabs(last_z_position - is->z_position()) > ALIGN_EPSILON || last_direction != is->direction() ) { - text = make_shared<order::Text>(subtitle, is->h_align(), is->h_position(), is->v_align(), is->v_position(), is->z_position(), is->direction()); + text = make_shared<order::Text>( + subtitle, + is->h_align(), + is->h_position(), + is->v_align(), + is->v_position(), + is->z_position(), + is->direction(), + is->rubies() + ); subtitle->children.push_back (text); last_h_align = is->h_align (); diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h index 42030288..25758c2e 100644 --- a/src/subtitle_asset.h +++ b/src/subtitle_asset.h @@ -216,7 +216,13 @@ private: friend struct ::pull_fonts_test2; friend struct ::pull_fonts_test3; - void maybe_add_subtitle (std::string text, std::vector<ParseState> const & parse_state, float space_before, Standard standard); + void maybe_add_subtitle( + std::string text, + std::vector<ParseState> const & parse_state, + float space_before, + Standard standard, + std::vector<Ruby> const& rubies + ); static void pull_fonts (std::shared_ptr<order::Part> part); }; diff --git a/src/subtitle_asset_internal.cc b/src/subtitle_asset_internal.cc index bb67b45a..99d8411b 100644 --- a/src/subtitle_asset_internal.cc +++ b/src/subtitle_asset_internal.cc @@ -223,6 +223,18 @@ order::Text::as_xml (xmlpp::Element* parent, Context& context) const e->set_attribute ("Direction", direction_to_string (_direction)); } + for (auto const& ruby: _rubies) { + auto xml = e->add_child("Ruby"); + xml->add_child("Rb")->add_child_text(ruby.base); + auto rt = xml->add_child("Rt"); + rt->add_child_text(ruby.annotation); + rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6)); + rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after"); + rt->set_attribute("Offset", dcp::raw_convert<string>(ruby.offset, 6)); + rt->set_attribute("Spacing", dcp::raw_convert<string>(ruby.spacing, 6)); + rt->set_attribute("AspectAdjust", dcp::raw_convert<string>(ruby.aspect_adjust, 6)); + } + return e; } diff --git a/src/subtitle_asset_internal.h b/src/subtitle_asset_internal.h index adf2c994..557db2e4 100644 --- a/src/subtitle_asset_internal.h +++ b/src/subtitle_asset_internal.h @@ -62,6 +62,7 @@ struct pull_fonts_test3; namespace dcp { +class Ruby; class SubtitleString; @@ -145,7 +146,7 @@ private: class Text : public Part { public: - Text (std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction) + Text(std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction, std::vector<Ruby> rubies) : Part (parent) , _h_align (h_align) , _h_position (h_position) @@ -153,6 +154,7 @@ public: , _v_position (v_position) , _z_position(z_position) , _direction (direction) + , _rubies(rubies) {} xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override; @@ -164,6 +166,7 @@ private: float _v_position; float _z_position; Direction _direction; + std::vector<Ruby> _rubies; }; diff --git a/src/subtitle_string.cc b/src/subtitle_string.cc index b627240b..af61d928 100644 --- a/src/subtitle_string.cc +++ b/src/subtitle_string.cc @@ -49,6 +49,7 @@ using std::min; using std::ostream; using std::shared_ptr; using std::string; +using std::vector; using boost::optional; using namespace dcp; @@ -74,7 +75,8 @@ SubtitleString::SubtitleString ( Colour effect_colour, Time fade_up_time, Time fade_down_time, - float space_before + float space_before, + vector<Ruby> rubies ) : Subtitle(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time) , _font (font) @@ -89,6 +91,7 @@ SubtitleString::SubtitleString ( , _effect (effect) , _effect_colour (effect_colour) , _space_before (space_before) + , _rubies(rubies) { _aspect_adjust = max(min(_aspect_adjust, 4.0f), 0.25f); } @@ -130,7 +133,8 @@ dcp::operator== (SubtitleString const & a, SubtitleString const & b) a.effect_colour() == b.effect_colour() && a.fade_up_time() == b.fade_up_time() && a.fade_down_time() == b.fade_down_time() && - fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON + fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON && + a.rubies() == b.rubies() ); } @@ -175,6 +179,10 @@ dcp::operator<< (ostream& s, SubtitleString const & sub) << ", effect colour (" << sub.effect_colour().r << ", " << sub.effect_colour().g << ", " << sub.effect_colour().b << ")" << ", space before " << sub.space_before(); + for (auto ruby: sub.rubies()) { + s << ", ruby " << ruby.base << " " << ruby.annotation; + } + return s; } @@ -254,6 +262,11 @@ SubtitleString::equals(shared_ptr<const Subtitle> other_sub, EqualityOptions con same = false; } + if (_rubies != other->_rubies) { + note(NoteType::ERROR, "rubies differ"); + same = false; + } + return same; } diff --git a/src/subtitle_string.h b/src/subtitle_string.h index 4eb64a68..1ef57ff2 100644 --- a/src/subtitle_string.h +++ b/src/subtitle_string.h @@ -42,6 +42,7 @@ #include "dcp_time.h" +#include "ruby.h" #include "subtitle.h" #include <boost/optional.hpp> #include <string> @@ -100,7 +101,8 @@ public: Colour effect_colour, Time fade_up_time, Time fade_down_time, - float space_before + float space_before, + std::vector<Ruby> rubies ); /** @return font ID */ @@ -158,6 +160,10 @@ public: return _aspect_adjust; } + std::vector<Ruby> const& rubies() const { + return _rubies; + } + void set_font (std::string id) { _font = id; } @@ -190,6 +196,10 @@ public: _effect_colour = c; } + void set_rubies(std::vector<Ruby> rubies) { + _rubies = std::move(rubies); + } + bool equals(std::shared_ptr<const dcp::Subtitle> other_sub, EqualityOptions const& options, NoteHandler node) const override; private: @@ -213,6 +223,7 @@ private: Effect _effect; Colour _effect_colour; float _space_before; + std::vector<Ruby> _rubies; }; bool operator== (SubtitleString const & a, SubtitleString const & b); @@ -224,3 +235,4 @@ std::ostream& operator<< (std::ostream& s, SubtitleString const & sub); #endif + diff --git a/src/types.h b/src/types.h index bcb6da5b..183ce638 100644 --- a/src/types.h +++ b/src/types.h @@ -277,6 +277,11 @@ constexpr float ALIGN_EPSILON = 1e-3; constexpr float SPACE_BEFORE_EPSILON = 1e-3; +constexpr float SIZE_EPSILON = 1e-3; +constexpr float OFFSET_EPSILON = 1e-3; +constexpr float SPACING_EPSILON = 1e-3; + + enum class Marker { FFOC, ///< first frame of composition LFOC, ///< last frame of composition diff --git a/src/wscript b/src/wscript index e6bee6f2..3bc8537b 100644 --- a/src/wscript +++ b/src/wscript @@ -101,6 +101,7 @@ def build(bld): reel_subtitle_asset.cc ref.cc rgb_xyz.cc + ruby.cc s_gamut3_transfer_function.cc search.cc smpte_load_font_node.cc @@ -205,6 +206,7 @@ def build(bld): reel_stereo_picture_asset.h reel_subtitle_asset.h ref.h + ruby.h s_gamut3_transfer_function.h search.h smpte_load_font_node.h |
