Bv2.1 7.2.4: first subtitle should be at least 4s into the DCP.
authorCarl Hetherington <cth@carlh.net>
Sat, 9 Jan 2021 00:39:08 +0000 (01:39 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 17 Jan 2021 19:13:22 +0000 (20:13 +0100)
src/verify.cc
src/verify.h
test/verify_test.cc

index 8f04f799b712e6397270749e3d0a7378a4009375..6a9967e60f4d35096c3cd382aa2458f571317d42 100644 (file)
@@ -651,7 +651,8 @@ verify_subtitle_asset (
        function<void (string, optional<boost::filesystem::path>)> stage,
        boost::filesystem::path xsd_dtd_directory,
        list<VerificationNote>& notes,
-       State& state
+       State& state,
+       bool first_reel
        )
 {
        stage ("Checking subtitle XML", asset->file());
@@ -715,6 +716,20 @@ verify_subtitle_asset (
                                        VerificationNote::VERIFY_BV21_ERROR, VerificationNote::SUBTITLE_START_TIME_NON_ZERO, *asset->file())
                                );
                }
+
+               if (first_reel) {
+                       auto subs = smpte->subtitles();
+                       subs.sort ([](shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) {
+                               return a->in() < b->in();
+                       });
+                       if (!subs.empty() && subs.front()->in() < dcp::Time(0, 0, 4, 0, 24)) {
+                               notes.push_back(
+                                       VerificationNote(
+                                               VerificationNote::VERIFY_WARNING, VerificationNote::FIRST_TEXT_TOO_EARLY
+                                               )
+                                       );
+                       }
+               }
        }
 }
 
@@ -725,10 +740,11 @@ verify_closed_caption_asset (
        function<void (string, optional<boost::filesystem::path>)> stage,
        boost::filesystem::path xsd_dtd_directory,
        list<VerificationNote>& notes,
-       State& state
+       State& state,
+       bool first_reel
        )
 {
-       verify_subtitle_asset (asset, stage, xsd_dtd_directory, notes, state);
+       verify_subtitle_asset (asset, stage, xsd_dtd_directory, notes, state, first_reel);
 
        if (asset->raw_xml().size() > 256 * 1024) {
                notes.push_back (
@@ -796,6 +812,7 @@ dcp::verify (
                                }
                        }
 
+                       bool first_reel = true;
                        for (auto reel: cpl->reels()) {
                                stage ("Checking reel", optional<boost::filesystem::path>());
 
@@ -834,16 +851,18 @@ dcp::verify (
                                if (reel->main_subtitle()) {
                                        verify_main_subtitle_reel (reel->main_subtitle(), notes);
                                        if (reel->main_subtitle()->asset_ref().resolved()) {
-                                               verify_subtitle_asset (reel->main_subtitle()->asset(), stage, xsd_dtd_directory, notes, state);
+                                               verify_subtitle_asset (reel->main_subtitle()->asset(), stage, xsd_dtd_directory, notes, state, first_reel);
                                        }
                                }
 
                                for (auto i: reel->closed_captions()) {
                                        verify_closed_caption_reel (i, notes);
                                        if (i->asset_ref().resolved()) {
-                                               verify_closed_caption_asset (i->asset(), stage, xsd_dtd_directory, notes, state);
+                                               verify_closed_caption_asset (i->asset(), stage, xsd_dtd_directory, notes, state, first_reel);
                                        }
                                }
+
+                               first_reel = false;
                        }
                }
 
@@ -927,6 +946,8 @@ dcp::note_to_string (dcp::VerificationNote note)
                return String::compose("The XML for a SMPTE subtitle asset has no <StartTime> tag, which is required by Bv2.1", note.file()->filename());
        case dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO:
                return String::compose("The XML for a SMPTE subtitle asset has a non-zero <StartTime> tag, which is disallowed by Bv2.1", note.file()->filename());
+       case dcp::VerificationNote::FIRST_TEXT_TOO_EARLY:
+               return "The first subtitle or closed caption is less than 4 seconds from the start of the DCP.";
        }
 
        return "";
index 2957654fc3a917a3043f9990d0193fe3f1cbcfb6..f692e867ed288bf852adb4a2889930f802c265dc 100644 (file)
@@ -116,6 +116,8 @@ public:
                MISSING_SUBTITLE_START_TIME,
                /** Some SMPTE subtitle XML has a non-zero <StartTime> tag [Bv2.1_7.2.3] */
                SUBTITLE_START_TIME_NON_ZERO,
+               /** The first subtitle or closed caption happens before 4s into the first reel [Bv2.1_7.2.4] */
+               FIRST_TEXT_TOO_EARLY,
        };
 
        VerificationNote (Type type, Code code)
index 4cde39668d66f2ff563c376f0314206eb31a9144..863ff9f7b5797e6c6e25255570184b3a7158e167 100644 (file)
@@ -61,6 +61,7 @@ using std::pair;
 using std::string;
 using std::vector;
 using std::make_pair;
+using std::make_shared;
 using boost::optional;
 using std::shared_ptr;
 
@@ -1163,6 +1164,36 @@ BOOST_AUTO_TEST_CASE (verify_picture_size)
 }
 
 
+static
+void
+add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame)
+{
+       asset->add (
+               make_shared<dcp::SubtitleString>(
+                       optional<string>(),
+                       false,
+                       false,
+                       false,
+                       dcp::Colour(),
+                       42,
+                       1,
+                       dcp::Time(start_frame, 24, 24),
+                       dcp::Time(end_frame, 24, 24),
+                       0,
+                       dcp::HALIGN_CENTER,
+                       0,
+                       dcp::VALIGN_CENTER,
+                       dcp::DIRECTION_LTR,
+                       "Hello",
+                       dcp::NONE,
+                       dcp::Colour(),
+                       dcp::Time(),
+                       dcp::Time()
+               )
+       );
+}
+
+
 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
 {
        boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
@@ -1170,31 +1201,7 @@ BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
 
        shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset());
        for (int i = 0; i < 2048; ++i) {
-               asset->add (
-                       shared_ptr<dcp::Subtitle>(
-                               new dcp::SubtitleString(
-                                       optional<string>(),
-                                       false,
-                                       false,
-                                       false,
-                                       dcp::Colour(),
-                                       42,
-                                       1,
-                                       dcp::Time(i * 24, 24, 24),
-                                       dcp::Time(i * 24 + 20, 24, 24),
-                                       0,
-                                       dcp::HALIGN_CENTER,
-                                       0,
-                                       dcp::VALIGN_CENTER,
-                                       dcp::DIRECTION_LTR,
-                                       "Hello",
-                                       dcp::NONE,
-                                       dcp::Colour(),
-                                       dcp::Time(),
-                                       dcp::Time()
-                                       )
-                               )
-                       );
+               add_test_subtitle (asset, i * 24, i * 24 + 20);
        }
        asset->set_language (dcp::LanguageTag("de-DE"));
        asset->write (dir / "subs.mxf");
@@ -1210,10 +1217,12 @@ BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
        vector<boost::filesystem::path> dirs;
        dirs.push_back (dir);
        list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
-       BOOST_REQUIRE_EQUAL (notes.size(), 2U);
+       BOOST_REQUIRE_EQUAL (notes.size(), 3U);
        list<dcp::VerificationNote>::const_iterator i = notes.begin();
        BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
        ++i;
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
+       ++i;
        BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
        BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES);
 }
@@ -1240,31 +1249,7 @@ verify_timed_text_asset_too_large (string name)
        boost::filesystem::path const dir = boost::filesystem::path("build/test") / name;
        prepare_directory (dir);
        shared_ptr<dcp::SMPTESubtitleAsset> asset = make_large_subtitle_asset (dir / "font.ttf");
-       asset->add (
-               shared_ptr<dcp::Subtitle>(
-                       new dcp::SubtitleString(
-                               optional<string>(),
-                               false,
-                               false,
-                               false,
-                               dcp::Colour(),
-                               42,
-                               1,
-                               dcp::Time(0, 24, 24),
-                               dcp::Time(20, 24, 24),
-                               0,
-                               dcp::HALIGN_CENTER,
-                               0,
-                               dcp::VALIGN_CENTER,
-                               dcp::DIRECTION_LTR,
-                               "Hello",
-                               dcp::NONE,
-                               dcp::Colour(),
-                               dcp::Time(),
-                               dcp::Time()
-                               )
-                       )
-               );
+       add_test_subtitle (asset, 0, 20);
        asset->set_language (dcp::LanguageTag("de-DE"));
        asset->write (dir / "subs.mxf");
 
@@ -1280,7 +1265,8 @@ verify_timed_text_asset_too_large (string name)
        vector<boost::filesystem::path> dirs;
        dirs.push_back (dir);
        list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
-       BOOST_REQUIRE_EQUAL (notes.size(), 3U);
+       BOOST_REQUIRE_EQUAL (notes.size(), 4U);
+       dump_notes (notes);
        list<dcp::VerificationNote>::const_iterator i = notes.begin();
        BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
        BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES);
@@ -1289,6 +1275,8 @@ verify_timed_text_asset_too_large (string name)
        BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES);
        ++i;
        BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
+       ++i;
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
 }
 
 
@@ -1339,10 +1327,13 @@ BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
 
        vector<boost::filesystem::path> dirs;
        dirs.push_back (dir);
-       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
-       BOOST_REQUIRE_EQUAL (notes.size(), 1U);
-       BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
-       BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE);
+       auto notes = dcp::verify (dirs, &stage, &progress, xsd_test);
+       BOOST_REQUIRE_EQUAL (notes.size(), 2U);
+       auto i = notes.begin();
+       BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE);
+       ++i;
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
 }
 
 
@@ -1425,10 +1416,13 @@ BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
 
        vector<boost::filesystem::path> dirs;
        dirs.push_back (dir);
-       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
-       BOOST_REQUIRE_EQUAL (notes.size(), 1U);
-       BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
-       BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
+       auto notes = dcp::verify (dirs, &stage, &progress, xsd_test);
+       BOOST_REQUIRE_EQUAL (notes.size(), 2U);
+       auto i = notes.begin();
+       BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
+       ++i;
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
 }
 
 
@@ -1473,8 +1467,102 @@ BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
 
        vector<boost::filesystem::path> dirs;
        dirs.push_back (dir);
-       list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
+       auto notes = dcp::verify (dirs, &stage, &progress, xsd_test);
+       BOOST_REQUIRE_EQUAL (notes.size(), 2U);
+       auto i = notes.begin();
+       BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO);
+       ++i;
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_text_too_early)
+{
+       auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
+       prepare_directory (dir);
+       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       asset->set_start_time (dcp::Time());
+       /* Just too early */
+       add_test_subtitle (asset, 4 * 24 - 1, 5 * 24);
+       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 = make_shared<dcp::Reel>();
+       reel->add (reel_asset);
+       auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
+       cpl->add (reel);
+       auto dcp = make_shared<dcp::DCP>(dir);
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+
+       auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
        BOOST_REQUIRE_EQUAL (notes.size(), 1U);
-       BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
-       BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO);
+       auto i = notes.begin();
+       BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::FIRST_TEXT_TOO_EARLY);
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
+{
+       auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
+       prepare_directory (dir);
+       auto asset = make_shared<dcp::SMPTESubtitleAsset>();
+       asset->set_start_time (dcp::Time());
+       /* Just late enough */
+       add_test_subtitle (asset, 4 * 24, 5 * 24);
+       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 = make_shared<dcp::Reel>();
+       reel->add (reel_asset);
+       auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
+       cpl->add (reel);
+       auto dcp = make_shared<dcp::DCP>(dir);
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+
+       auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+       dump_notes (notes);
+       BOOST_REQUIRE (notes.empty());
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
+{
+       auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
+       prepare_directory (dir);
+
+       auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
+       asset1->set_start_time (dcp::Time());
+       /* Just late enough */
+       add_test_subtitle (asset1, 4 * 24, 5 * 24);
+       asset1->set_language (dcp::LanguageTag("de-DE"));
+       asset1->write (dir / "subs.mxf");
+       auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
+       auto reel1 = make_shared<dcp::Reel>();
+       reel1->add (reel_asset1);
+
+       auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
+       asset2->set_start_time (dcp::Time());
+       /* This would be too early on first reel but should be OK on the second */
+       add_test_subtitle (asset2, 0, 4 * 24);
+       asset2->set_language (dcp::LanguageTag("de-DE"));
+       asset2->write (dir / "subs.mxf");
+       auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
+       auto reel2 = make_shared<dcp::Reel>();
+       reel2->add (reel_asset2);
+
+       auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
+       cpl->add (reel1);
+       cpl->add (reel2);
+       auto dcp = make_shared<dcp::DCP>(dir);
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+
+       auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+       dump_notes (notes);
+       BOOST_REQUIRE (notes.empty());
 }