diff options
| -rw-r--r-- | src/lib/reel_writer.cc | 29 | ||||
| -rw-r--r-- | src/lib/reel_writer.h | 4 | ||||
| -rw-r--r-- | src/lib/render_text.cc | 68 | ||||
| -rw-r--r-- | src/lib/render_text.h | 41 | ||||
| m--------- | test/data | 0 | ||||
| -rw-r--r-- | test/subtitle_position_test.cc | 164 | ||||
| -rw-r--r-- | test/wscript | 1 |
7 files changed, 303 insertions, 4 deletions
diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc index f9e29a16f..94b12ec7d 100644 --- a/src/lib/reel_writer.cc +++ b/src/lib/reel_writer.cc @@ -114,6 +114,7 @@ ReelWriter::ReelWriter ( , _content_summary (film()->content_summary(period)) , _job (job) , _text_only (text_only) + , _font_metrics(film()->frame_size().height) { /* Create or find our picture asset in a subdirectory, named according to those film's parameters which affect the video @@ -889,6 +890,33 @@ ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track, bool } +float +ReelWriter::convert_vertical_position(StringText const& subtitle, dcp::Standard to) const +{ + if (subtitle.valign_standard == to) { + return subtitle.v_position(); + } + + auto const baseline_to_bottom = _font_metrics.baseline_to_bottom(subtitle); + auto const height = _font_metrics.height(subtitle); + + float correction = 0; + switch (subtitle.v_align()) { + case dcp::VAlign::TOP: + correction = height - baseline_to_bottom; + break; + case dcp::VAlign::CENTER: + correction = (height / 2) - baseline_to_bottom; + break; + case dcp::VAlign::BOTTOM: + correction = baseline_to_bottom; + break; + } + + return subtitle.v_position() + ((subtitle.valign_standard == dcp::Standard::SMPTE) ? correction : -correction); +} + + void ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period, FontIdMap const& fonts) { @@ -928,6 +956,7 @@ ReelWriter::write (PlayerText subs, TextType type, optional<DCPTextTrack> track, for (auto i: subs.string) { i.set_in (dcp::Time(period.from.seconds() - _period.from.seconds(), tcr)); i.set_out (dcp::Time(period.to.seconds() - _period.from.seconds(), tcr)); + i.set_v_position(convert_vertical_position(i, film()->interop() ? dcp::Standard::INTEROP : dcp::Standard::SMPTE)); auto sub = make_shared<dcp::SubtitleString>(i); if (type == TextType::OPEN_SUBTITLE) { sub->set_font(fonts.get(i.font)); diff --git a/src/lib/reel_writer.h b/src/lib/reel_writer.h index e2c713cc5..d5b531f41 100644 --- a/src/lib/reel_writer.h +++ b/src/lib/reel_writer.h @@ -26,6 +26,7 @@ #include "font_id_map.h" #include "player_text.h" #include "referenced_reel_asset.h" +#include "render_text.h" #include "types.h" #include "weak_film.h" #include <dcp/atmos_asset_writer.h> @@ -121,6 +122,7 @@ private: std::set<DCPTextTrack> ensure_closed_captions ) const; void create_reel_markers (std::shared_ptr<dcp::Reel> reel) const; + float convert_vertical_position(StringText const& subtitle, dcp::Standard to) const; dcpomatic::DCPTimePeriod _period; /** the first picture frame index that does not already exist in our MXF */ @@ -147,5 +149,7 @@ private: std::shared_ptr<dcp::AtmosAsset> _atmos_asset; std::shared_ptr<dcp::AtmosAssetWriter> _atmos_asset_writer; + mutable FontMetrics _font_metrics; + static int const _info_size; }; diff --git a/src/lib/render_text.cc b/src/lib/render_text.cc index 9c1c233c2..d3db4ac19 100644 --- a/src/lib/render_text.cc +++ b/src/lib/render_text.cc @@ -171,12 +171,12 @@ create_surface (shared_ptr<Image> image) static string -setup_font (StringText const& subtitle) +setup_font(shared_ptr<const dcpomatic::Font> font) { auto font_file = default_font_file (); - if (subtitle.font && subtitle.font->file()) { - font_file = *subtitle.font->file(); + if (font && font->file()) { + font_file = *font->file(); } return FontConfig::instance()->make_font_available(font_file); @@ -299,7 +299,7 @@ render_line (list<StringText> subtitles, dcp::Size target, DCPTime time, int fra DCPOMATIC_ASSERT (!subtitles.empty ()); auto const& first = subtitles.front (); - auto const font_name = setup_font (first); + auto const font_name = setup_font(first.font); auto const fade_factor = calculate_fade_factor (first, time, frame_rate); auto const markup = marked_up (subtitles, target.height, fade_factor, font_name); auto layout = create_layout (); @@ -402,3 +402,63 @@ render_text (list<StringText> subtitles, dcp::Size target, DCPTime time, int fra return images; } + + +float +FontMetrics::height(StringText const& subtitle) +{ + return get(subtitle)->second.second; +} + + +float +FontMetrics::baseline_to_bottom(StringText const& subtitle) +{ + return get(subtitle)->second.first; +} + + +FontMetrics::Cache::iterator +FontMetrics::get(StringText const& subtitle) +{ + auto id = Identifier(subtitle); + + auto iter = _cache.find(id); + if (iter != _cache.end()) { + return iter; + } + + auto const font_name = setup_font(subtitle.font); + auto layout = create_layout(); + auto copy = subtitle; + copy.set_text("Qypjg"); + setup_layout(layout, font_name, marked_up({copy}, _target_height, 1, font_name)); + auto ink = layout->get_ink_extents(); + auto const scale = float(_target_height * Pango::SCALE); + return _cache.insert({id, { ink.get_y() / scale, ink.get_height() / scale}}).first; +} + + +FontMetrics::Identifier::Identifier(StringText const& subtitle) + : font(subtitle.font) + , size(subtitle.size()) + , aspect_adjust(subtitle.aspect_adjust()) +{ + +} + + +bool +FontMetrics::Identifier::operator<(FontMetrics::Identifier const& other) const +{ + if (font != other.font) { + return font < other.font; + } + + if (size != other.size) { + return size < other.size; + } + + return aspect_adjust < other.aspect_adjust; +} + diff --git a/src/lib/render_text.h b/src/lib/render_text.h index 6ff91dce1..762d79446 100644 --- a/src/lib/render_text.h +++ b/src/lib/render_text.h @@ -18,14 +18,55 @@ */ + #include "position_image.h" #include "dcpomatic_time.h" #include "string_text.h" #include <dcp/util.h> +#include <memory> + namespace dcpomatic { class Font; } + std::string marked_up (std::list<StringText> subtitles, int target_height, float fade_factor, std::string font_name); std::list<PositionImage> render_text (std::list<StringText>, dcp::Size, dcpomatic::DCPTime, int); + + +class FontMetrics +{ +public: + FontMetrics(int target_height) + : _target_height(target_height) + {} + + float baseline_to_bottom(StringText const& subtitle); + float height(StringText const& subtitle); + +private: + /** Class to collect the properties of a subtitle which affect the metrics we care about + * i.e. baseline position and height. + */ + class Identifier + { + public: + Identifier(StringText const& subtitle); + + std::shared_ptr<dcpomatic::Font> font; + int size; + float aspect_adjust; + + bool operator<(Identifier const& other) const; + }; + + using Cache = std::map<Identifier, std::pair<float, float>>; + + Cache::iterator get(StringText const& subtitle); + + Cache _cache; + + int _target_height; +}; + diff --git a/test/data b/test/data -Subproject 92d2787d602701d5faf53f0ab7430f62bec0916 +Subproject ef2374f03fbd715883da46bb3cb011befa11615 diff --git a/test/subtitle_position_test.cc b/test/subtitle_position_test.cc new file mode 100644 index 000000000..0237a417c --- /dev/null +++ b/test/subtitle_position_test.cc @@ -0,0 +1,164 @@ +/* + Copyright (C) 2022 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic 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. + + DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include "lib/content.h" +#include "lib/content_factory.h" +#include "lib/film.h" +#include "lib/make_dcp.h" +#include "lib/text_content.h" +#include "test.h" +#include <dcp/interop_subtitle_asset.h> +#include <dcp/smpte_subtitle_asset.h> +#include <dcp/language_tag.h> +#include <boost/test/unit_test.hpp> +#include <vector> + + +using std::string; +using std::vector; +using std::shared_ptr; + + +BOOST_AUTO_TEST_CASE(interop_correctly_placed_in_interop) +{ + string const name = "interop_in_interop_position_test"; + auto fr = content_factory("test/data/dcp_sub.xml"); + auto film = new_test_film2(name, fr); + + film->set_interop(true); + + make_and_verify_dcp ( + film, + { + dcp::VerificationNote::Code::INVALID_STANDARD, + dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING, + dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION + }); + + auto output = subtitle_file(film); + dcp::InteropSubtitleAsset asset(output); + auto output_subs = asset.subtitles(); + BOOST_REQUIRE_EQUAL(output_subs.size(), 1U); + + /* The input subtitle should have been left alone */ + BOOST_CHECK(output_subs[0]->v_align() == dcp::VAlign::BOTTOM); + BOOST_CHECK_CLOSE(output_subs[0]->v_position(), 0.08, 1e-3); +} + + +BOOST_AUTO_TEST_CASE(interop_correctly_placed_in_smpte) +{ + string const name = "interop_in_smpte_position_test"; + auto fr = content_factory("test/data/dcp_sub.xml"); + auto film = new_test_film2(name, fr); + + film->set_interop(false); + + make_and_verify_dcp ( + film, + { + dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING, + dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, + dcp::VerificationNote::Code::MISSING_CPL_METADATA, + dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + }); + + auto output = subtitle_file(film); + dcp::SMPTESubtitleAsset asset(output); + auto output_subs = asset.subtitles(); + BOOST_REQUIRE_EQUAL(output_subs.size(), 1U); + + BOOST_CHECK(output_subs[0]->v_align() == dcp::VAlign::BOTTOM); + BOOST_CHECK_CLOSE(output_subs[0]->v_position(), 0.07074, 1e-3); +} + + +BOOST_AUTO_TEST_CASE(smpte_correctly_placed_in_interop) +{ + string const name = "smpte_in_interop_position_test"; + auto fr = content_factory("test/data/short.srt"); + auto film = new_test_film2(name, fr); + + film->set_interop(true); + + make_and_verify_dcp ( + film, + { + dcp::VerificationNote::Code::INVALID_STANDARD, + dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING, + dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME + }); + + auto output = subtitle_file(film); + dcp::InteropSubtitleAsset asset(output); + auto output_subs = asset.subtitles(); + BOOST_REQUIRE_EQUAL(output_subs.size(), 1U); + + BOOST_CHECK(output_subs[0]->v_align() == dcp::VAlign::TOP); + BOOST_CHECK_CLOSE(output_subs[0]->v_position(), 0.87079, 1e-3); +} + + +static +void +vpos_test(dcp::VAlign reference, float position, dcp::Standard from, dcp::Standard to) +{ + string standard = from == dcp::Standard::INTEROP ? "interop" : "smpte"; + auto name = String::compose("vpos_test_%1_%2", standard, valign_to_string(reference)); + auto in = content_factory(String::compose("test/data/%1.xml", name)); + auto film = new_test_film2(name, in); + + film->set_interop(to == dcp::Standard::INTEROP); + + film->write_metadata(); + make_dcp(film, TranscodeJob::ChangedBehaviour::IGNORE); + BOOST_REQUIRE(!wait_for_jobs()); + + auto out = subtitle_file(film); + vector<shared_ptr<const dcp::Subtitle>> subtitles; + if (to == dcp::Standard::INTEROP) { + dcp::InteropSubtitleAsset asset(out); + subtitles = asset.subtitles(); + } else { + dcp::SMPTESubtitleAsset asset(out); + subtitles = asset.subtitles(); + } + + BOOST_REQUIRE_EQUAL(subtitles.size(), 1U); + + BOOST_CHECK(subtitles[0]->v_align() == reference); + BOOST_CHECK_CLOSE(subtitles[0]->v_position(), position, 1e-3); +} + + +BOOST_AUTO_TEST_CASE(subtitles_correctly_placed_with_all_references) +{ + constexpr auto baseline_to_bottom = 0.00925926; + constexpr auto height = 0.0462963; + + vpos_test(dcp::VAlign::TOP, 0.2 - height + baseline_to_bottom, dcp::Standard::INTEROP, dcp::Standard::SMPTE); + vpos_test(dcp::VAlign::CENTER, 0.11 - (height / 2) + baseline_to_bottom, dcp::Standard::INTEROP, dcp::Standard::SMPTE); + vpos_test(dcp::VAlign::BOTTOM, 0.08 - baseline_to_bottom, dcp::Standard::INTEROP, dcp::Standard::SMPTE); + vpos_test(dcp::VAlign::TOP, 0.1 + height - baseline_to_bottom, dcp::Standard::SMPTE, dcp::Standard::INTEROP); + vpos_test(dcp::VAlign::CENTER, 0.15 + (height / 2) - baseline_to_bottom, dcp::Standard::SMPTE, dcp::Standard::INTEROP); + vpos_test(dcp::VAlign::BOTTOM, 0.10 + baseline_to_bottom, dcp::Standard::SMPTE, dcp::Standard::INTEROP); +} + diff --git a/test/wscript b/test/wscript index fc63aac1e..e54d5c985 100644 --- a/test/wscript +++ b/test/wscript @@ -141,6 +141,7 @@ def build(bld): subtitle_font_id_change_test.cc subtitle_language_test.cc subtitle_metadata_test.cc + subtitle_position_test.cc subtitle_reel_test.cc subtitle_reel_number_test.cc subtitle_timing_test.cc |
