diff options
| author | Carl Hetherington <cth@carlh.net> | 2021-01-13 00:53:38 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2021-01-17 20:13:23 +0100 |
| commit | 5c57052dfeda55dc218001b089ebcdaaf6bedc3b (patch) | |
| tree | df73c99b6b3b970939ae433e169ca51bc911d64e | |
| parent | 845a6f2855541112e33ba6b07ea8e7e641c1b0d0 (diff) | |
Bv2.1 7.2.{6,7}: various limits on subtitle line and character counts.
| -rw-r--r-- | src/verify.cc | 141 | ||||
| -rw-r--r-- | src/verify.h | 10 | ||||
| -rw-r--r-- | test/verify_test.cc | 213 |
3 files changed, 341 insertions, 23 deletions
diff --git a/src/verify.cc b/src/verify.cc index a1d12e9b..d1b4988a 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -835,6 +835,105 @@ check_text_timing ( } +struct LinesCharactersResult +{ + bool warning_length_exceeded = false; + bool error_length_exceeded = false; + bool line_count_exceeded = false; +}; + + +static +void +check_text_lines_and_characters ( + shared_ptr<SubtitleAsset> asset, + int warning_length, + int error_length, + LinesCharactersResult* result + ) +{ + class Event + { + public: + Event (dcp::Time time_, float position_, int characters_) + : time (time_) + , position (position_) + , characters (characters_) + {} + + Event (dcp::Time time_, shared_ptr<Event> start_) + : time (time_) + , start (start_) + {} + + dcp::Time time; + int position; //< position from 0 at top of screen to 100 at bottom + int characters; + shared_ptr<Event> start; + }; + + vector<shared_ptr<Event>> events; + + auto position = [](shared_ptr<const SubtitleString> sub) { + switch (sub->v_align()) { + case VALIGN_TOP: + return lrintf(sub->v_position() * 100); + case VALIGN_CENTER: + return lrintf((0.5f + sub->v_position()) * 100); + case VALIGN_BOTTOM: + return lrintf((1.0f - sub->v_position()) * 100); + } + + return 0L; + }; + + for (auto j: asset->subtitles()) { + auto text = dynamic_pointer_cast<const SubtitleString>(j); + if (text) { + auto in = make_shared<Event>(text->in(), position(text), text->text().length()); + events.push_back(in); + events.push_back(make_shared<Event>(text->out(), in)); + } + } + + std::sort(events.begin(), events.end(), [](shared_ptr<Event> const& a, shared_ptr<Event>const& b) { + return a->time < b->time; + }); + + map<int, int> current; + for (auto i: events) { + if (current.size() > 3) { + result->line_count_exceeded = true; + } + for (auto j: current) { + if (j.second >= warning_length) { + result->warning_length_exceeded = true; + } + if (j.second >= error_length) { + result->error_length_exceeded = true; + } + } + + if (i->start) { + /* end of a subtitle */ + DCP_ASSERT (current.find(i->start->position) != current.end()); + if (current[i->start->position] == i->start->characters) { + current.erase(i->start->position); + } else { + current[i->start->position] -= i->start->characters; + } + } else { + /* start of a subtitle */ + if (current.find(i->position) == current.end()) { + current[i->position] = i->characters; + } else { + current[i->position] += i->characters; + } + } + } +} + + static void check_text_timing (vector<shared_ptr<dcp::Reel>> reels, vector<VerificationNote>& notes) @@ -980,6 +1079,38 @@ dcp::verify ( if (dcp->standard() == dcp::SMPTE) { check_text_timing (cpl->reels(), notes); + + LinesCharactersResult result; + for (auto reel: cpl->reels()) { + if (reel->main_subtitle() && reel->main_subtitle()->asset()) { + check_text_lines_and_characters (reel->main_subtitle()->asset(), 52, 79, &result); + } + } + + if (result.line_count_exceeded) { + notes.push_back (VerificationNote(VerificationNote::VERIFY_WARNING, VerificationNote::TOO_MANY_SUBTITLE_LINES)); + } + if (result.error_length_exceeded) { + notes.push_back (VerificationNote(VerificationNote::VERIFY_WARNING, VerificationNote::SUBTITLE_LINE_TOO_LONG)); + } else if (result.warning_length_exceeded) { + notes.push_back (VerificationNote(VerificationNote::VERIFY_WARNING, VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED)); + } + + result = LinesCharactersResult(); + for (auto reel: cpl->reels()) { + for (auto i: reel->closed_captions()) { + if (i->asset()) { + check_text_lines_and_characters (i->asset(), 32, 32, &result); + } + } + } + + if (result.line_count_exceeded) { + notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES)); + } + if (result.error_length_exceeded) { + notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG)); + } } } @@ -1069,6 +1200,16 @@ dcp::note_to_string (dcp::VerificationNote note) return "At least one subtitle is less than the minimum of 15 frames suggested by Bv2.1"; case dcp::VerificationNote::SUBTITLE_TOO_CLOSE: return "At least one pair of subtitles are separated by less than the the minimum of 2 frames suggested by Bv2.1"; + case dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES: + return "There are more than 3 subtitle lines in at least one place in the DCP, which Bv2.1 advises against."; + case dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED: + return "There are more than 52 characters in at least one subtitle line, which Bv2.1 advises against."; + case dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG: + return "There are more than 79 characters in at least one subtitle line, which Bv2.1 strongly advises against."; + case dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES: + return "There are more than 3 closed caption lines in at least one place, which is disallowed by Bv2.1"; + case dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG: + return "There are more than 32 characters in at least one closed caption line, which is disallowed by Bv2.1"; } return ""; diff --git a/src/verify.h b/src/verify.h index 526b767a..0398c909 100644 --- a/src/verify.h +++ b/src/verify.h @@ -121,6 +121,16 @@ public: SUBTITLE_TOO_SHORT, /** At least one pair of subtitles are separated by less than the the minimum of 2 frames suggested by [Bv2.1_7.2.5] */ SUBTITLE_TOO_CLOSE, + /** There are more than 3 subtitle lines in at least one place [Bv2.1_7.2.7] */ + TOO_MANY_SUBTITLE_LINES, + /** There are more than 52 characters in at least one subtitle line [Bv2.1_7.2.7] */ + SUBTITLE_LINE_LONGER_THAN_RECOMMENDED, + /** There are more than 79 characters in at least one subtitle line [Bv2.1_7.2.7] */ + SUBTITLE_LINE_TOO_LONG, + /** There are more than 3 closed caption lines in at least one place [Bv2.1_7.2.6] */ + TOO_MANY_CLOSED_CAPTION_LINES, + /** There are more than 32 characters in at least one closed caption line [Bv2.1_7.2.6] */ + CLOSED_CAPTION_LINE_TOO_LONG, }; VerificationNote (Type type, Code code) diff --git a/test/verify_test.cc b/test/verify_test.cc index 7e80f899..1877e284 100644 --- a/test/verify_test.cc +++ b/test/verify_test.cc @@ -1037,7 +1037,7 @@ BOOST_AUTO_TEST_CASE (verify_picture_size) static void -add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame) +add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello") { asset->add ( make_shared<dcp::SubtitleString>( @@ -1052,10 +1052,10 @@ add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int en dcp::Time(end_frame, 24, 24), 0, dcp::HALIGN_CENTER, - 0, + v_position, dcp::VALIGN_CENTER, dcp::DIRECTION_LTR, - "Hello", + text, dcp::NONE, dcp::Colour(), dcp::Time(), @@ -1313,20 +1313,37 @@ BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml) } -static +class TestText +{ +public: + TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello") + : in(in_) + , out(out_) + , v_position(v_position_) + , text(text_) + {} + + int in; + int out; + float v_position; + string text; +}; + + +template <class T> void -dcp_with_subtitles (boost::filesystem::path dir, vector<int> timings) +dcp_with_text (boost::filesystem::path dir, vector<TestText> subs) { prepare_directory (dir); auto asset = make_shared<dcp::SMPTESubtitleAsset>(); asset->set_start_time (dcp::Time()); - for (auto i = 0U; i < timings.size(); i += 2) { - add_test_subtitle (asset, timings[i], timings[i + 1]); + for (auto i: subs) { + add_test_subtitle (asset, i.in, i.out, i.v_position, i.text); } asset->set_language (dcp::LanguageTag("de-DE")); asset->write (dir / "subs.mxf"); - auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0); + auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0); write_dcp_with_single_asset (dir, reel_asset); } @@ -1335,7 +1352,7 @@ BOOST_AUTO_TEST_CASE (verify_text_too_early) { auto const dir = boost::filesystem::path("build/test/verify_text_too_early"); /* Just too early */ - dcp_with_subtitles (dir, { 4 * 24 - 1, 5 * 24 }); + dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }}); check_verify_result ( { dir }, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }}); @@ -1346,7 +1363,7 @@ BOOST_AUTO_TEST_CASE (verify_text_not_too_early) { auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early"); /* Just late enough */ - dcp_with_subtitles (dir, { 4 * 24, 5 * 24 }); + dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }}); auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); BOOST_REQUIRE (notes.empty()); } @@ -1392,11 +1409,11 @@ BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel) BOOST_AUTO_TEST_CASE (verify_text_too_close) { auto const dir = boost::filesystem::path("build/test/verify_text_too_close"); - dcp_with_subtitles ( + dcp_with_text<dcp::ReelSubtitleAsset> ( dir, { - 4 * 24, 5 * 24, - 5 * 24 + 1, 6 * 24, + { 4 * 24, 5 * 24 }, + { 5 * 24 + 1, 6 * 24 }, }); check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_CLOSE }}); } @@ -1405,11 +1422,11 @@ BOOST_AUTO_TEST_CASE (verify_text_too_close) BOOST_AUTO_TEST_CASE (verify_text_not_too_close) { auto const dir = boost::filesystem::path("build/test/verify_text_not_too_close"); - dcp_with_subtitles ( + dcp_with_text<dcp::ReelSubtitleAsset> ( dir, { - 4 * 24, 5 * 24, - 5 * 24 + 16, 8 * 24, + { 4 * 24, 5 * 24 }, + { 5 * 24 + 16, 8 * 24 }, }); auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); BOOST_REQUIRE (notes.empty()); @@ -1419,11 +1436,7 @@ BOOST_AUTO_TEST_CASE (verify_text_not_too_close) BOOST_AUTO_TEST_CASE (verify_text_too_short) { auto const dir = boost::filesystem::path("build/test/verify_text_too_short"); - dcp_with_subtitles ( - dir, - { - 4 * 24, 4 * 24 + 1, - }); + dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }}); check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_SHORT }}); } @@ -1431,12 +1444,166 @@ BOOST_AUTO_TEST_CASE (verify_text_too_short) BOOST_AUTO_TEST_CASE (verify_text_not_too_short) { auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short"); - dcp_with_subtitles ( + dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }}); + auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + BOOST_REQUIRE (notes.empty()); +} + + +BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1) +{ + auto const dir = boost::filesystem::path ("verify_too_many_subtitle_lines1"); + dcp_with_text<dcp::ReelSubtitleAsset> ( + dir, + { + { 96, 200, 0.0, "We" }, + { 96, 200, 0.1, "have" }, + { 96, 200, 0.2, "four" }, + { 96, 200, 0.3, "lines" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}}); +} + + +BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1) +{ + auto const dir = boost::filesystem::path ("verify_not_too_many_subtitle_lines1"); + dcp_with_text<dcp::ReelSubtitleAsset> ( + dir, + { + { 96, 200, 0.0, "We" }, + { 96, 200, 0.1, "have" }, + { 96, 200, 0.2, "four" }, + }); + auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + BOOST_REQUIRE (notes.empty()); +} + + +BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2) +{ + auto const dir = boost::filesystem::path ("verify_too_many_subtitle_lines2"); + dcp_with_text<dcp::ReelSubtitleAsset> ( + dir, + { + { 96, 300, 0.0, "We" }, + { 96, 300, 0.1, "have" }, + { 150, 180, 0.2, "four" }, + { 150, 180, 0.3, "lines" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}}); +} + + +BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2) +{ + auto const dir = boost::filesystem::path ("verify_not_too_many_subtitle_lines2"); + dcp_with_text<dcp::ReelSubtitleAsset> ( dir, { - 4 * 24, 4 * 24 + 17, + { 96, 300, 0.0, "We" }, + { 96, 300, 0.1, "have" }, + { 150, 180, 0.2, "four" }, + { 190, 250, 0.3, "lines" } }); auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); BOOST_REQUIRE (notes.empty()); } + +BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1) +{ + auto const dir = boost::filesystem::path ("verify_subtitle_lines_too_long1"); + dcp_with_text<dcp::ReelSubtitleAsset> ( + dir, + { + { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED }}); +} + + +BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2) +{ + auto const dir = boost::filesystem::path ("verify_subtitle_lines_too_long2"); + dcp_with_text<dcp::ReelSubtitleAsset> ( + dir, + { + { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG }}); +} + + +BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1) +{ + auto const dir = boost::filesystem::path ("verify_too_many_closed_caption_lines1"); + dcp_with_text<dcp::ReelClosedCaptionAsset> ( + dir, + { + { 96, 200, 0.0, "We" }, + { 96, 200, 0.1, "have" }, + { 96, 200, 0.2, "four" }, + { 96, 200, 0.3, "lines" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}}); +} + + +BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1) +{ + auto const dir = boost::filesystem::path ("verify_not_too_many_closed_caption_lines1"); + dcp_with_text<dcp::ReelClosedCaptionAsset> ( + dir, + { + { 96, 200, 0.0, "We" }, + { 96, 200, 0.1, "have" }, + { 96, 200, 0.2, "four" }, + }); + auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + BOOST_REQUIRE (notes.empty()); +} + + +BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2) +{ + auto const dir = boost::filesystem::path ("verify_too_many_closed_caption_lines2"); + dcp_with_text<dcp::ReelClosedCaptionAsset> ( + dir, + { + { 96, 300, 0.0, "We" }, + { 96, 300, 0.1, "have" }, + { 150, 180, 0.2, "four" }, + { 150, 180, 0.3, "lines" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}}); +} + + +BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2) +{ + auto const dir = boost::filesystem::path ("verify_not_too_many_closed_caption_lines2"); + dcp_with_text<dcp::ReelClosedCaptionAsset> ( + dir, + { + { 96, 300, 0.0, "We" }, + { 96, 300, 0.1, "have" }, + { 150, 180, 0.2, "four" }, + { 190, 250, 0.3, "lines" } + }); + auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test); + BOOST_REQUIRE (notes.empty()); +} + + +BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1) +{ + auto const dir = boost::filesystem::path ("verify_closed_caption_lines_too_long1"); + dcp_with_text<dcp::ReelClosedCaptionAsset> ( + dir, + { + { 96, 300, 0.0, "0123456789012345678901234567890123" } + }); + check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG }}); +} + |
