Bv2.1 8.3.1: MainSubtitles must be in all reels (if they are there at
authorCarl Hetherington <cth@carlh.net>
Thu, 14 Jan 2021 23:17:09 +0000 (00:17 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 17 Jan 2021 19:13:23 +0000 (20:13 +0100)
all) and ClosedCaptions must have the same count on all reels.

src/verify.cc
src/verify.h
test/verify_test.cc

index a7f3f1da9a58cd8e2b8fe80903c95cf981c6872a..f8ab3d446560358a3fa46c2418106dba5f3284a4 100644 (file)
@@ -1053,6 +1053,15 @@ dcp::verify (
                                }
                        }
 
+                       /* set to true if any reel has a MainSubtitle */
+                       auto have_main_subtitle = false;
+                       /* set to true if any reel has no MainSubtitle */
+                       auto have_no_main_subtitle = false;
+                       /* fewest number of closed caption assets seen in a reel */
+                       size_t fewest_closed_captions = SIZE_MAX;
+                       /* most number of closed caption assets seen in a reel */
+                       size_t most_closed_captions = 0;
+
                        for (auto reel: cpl->reels()) {
                                stage ("Checking reel", optional<boost::filesystem::path>());
 
@@ -1105,6 +1114,9 @@ dcp::verify (
                                        if (reel->main_subtitle()->asset_ref().resolved()) {
                                                verify_subtitle_asset (reel->main_subtitle()->asset(), stage, xsd_dtd_directory, notes, state);
                                        }
+                                       have_main_subtitle = true;
+                               } else {
+                                       have_no_main_subtitle = true;
                                }
 
                                for (auto i: reel->closed_captions()) {
@@ -1113,9 +1125,21 @@ dcp::verify (
                                                verify_closed_caption_asset (i->asset(), stage, xsd_dtd_directory, notes, state);
                                        }
                                }
+
+                               fewest_closed_captions = std::min (fewest_closed_captions, reel->closed_captions().size());
+                               most_closed_captions = std::max (most_closed_captions, reel->closed_captions().size());
                        }
 
                        if (dcp->standard() == dcp::SMPTE) {
+
+                               if (have_main_subtitle && have_no_main_subtitle) {
+                                       notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS});
+                               }
+
+                               if (fewest_closed_captions != most_closed_captions) {
+                                       notes.push_back ({VerificationNote::VERIFY_BV21_ERROR, VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER});
+                               }
+
                                check_text_timing (cpl->reels(), notes);
 
                                LinesCharactersResult result;
@@ -1256,6 +1280,10 @@ dcp::note_to_string (dcp::VerificationNote note)
                return "The CPL's <AnnotationText> differs from its <ContentTitleText>, which Bv2.1 advises against.";
        case dcp::VerificationNote::MISMATCHED_ASSET_DURATION:
                return "All assets in a reel do not have the same duration, which is required by Bv2.1";
+       case dcp::VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS:
+               return "At least one reel contains a subtitle asset, but some reel(s) do not";
+       case dcp::VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER:
+               return "At least one reel has closed captions, but reels have different numbers of closed caption assets.";
        }
 
        return "";
index 3b2629f996414cb9e081bd59aadb832363b8192d..64232a43e2486114f95a5e65112cf5f9352066a7 100644 (file)
@@ -139,6 +139,10 @@ public:
                CPL_ANNOTATION_TEXT_DIFFERS_FROM_CONTENT_TITLE_TEXT,
                /** At least one asset in a reel does not have the same duration as the others */
                MISMATCHED_ASSET_DURATION,
+               /** If one reel has a MainSubtitle, all must have them */
+               MAIN_SUBTITLE_NOT_IN_ALL_REELS,
+               /** If one reel has at least one ClosedCaption, all reels must have the same number of ClosedCaptions */
+               CLOSED_CAPTION_ASSET_COUNTS_DIFFER,
        };
 
        VerificationNote (Type type, Code code)
index 86b98d2be620a4dd8c3cb2ad0ca45b26cedd937d..246b44620b451007892ab209492a7aabe019251c 100644 (file)
@@ -1698,3 +1698,139 @@ BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match)
        check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION }});
 }
 
+
+
+static
+void
+verify_subtitles_must_be_in_all_reels_check (boost::filesystem::path dir, bool add_to_reel1, bool add_to_reel2)
+{
+       boost::filesystem::remove_all (dir);
+       boost::filesystem::create_directories (dir);
+       auto dcp = make_shared<dcp::DCP>(dir);
+       auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::FEATURE);
+
+       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       subs->set_language (dcp::LanguageTag("de-DE"));
+       subs->set_start_time (dcp::Time());
+       subs->add (simple_subtitle());
+       subs->write (dir / "subs.mxf");
+       auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
+
+       auto reel1 = make_shared<dcp::Reel>(
+               make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
+               make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
+               );
+
+       if (add_to_reel1) {
+               reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
+       }
+
+       cpl->add (reel1);
+
+
+       auto reel2 = make_shared<dcp::Reel>(
+               make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
+               make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
+               );
+
+       if (add_to_reel2) {
+               reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
+       }
+
+       cpl->add (reel2);
+
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels)
+{
+       {
+               boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
+               verify_subtitles_must_be_in_all_reels_check (dir, true, false);
+               check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MAIN_SUBTITLE_NOT_IN_ALL_REELS}});
+       }
+
+       {
+               boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
+               verify_subtitles_must_be_in_all_reels_check (dir, true, true);
+               auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+               BOOST_REQUIRE (notes.empty());
+       }
+
+       {
+               boost::filesystem::path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
+               verify_subtitles_must_be_in_all_reels_check (dir, false, false);
+               auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+               BOOST_REQUIRE (notes.empty());
+       }
+}
+
+
+static
+void
+verify_closed_captions_must_be_in_all_reels_check (boost::filesystem::path dir, int caps_in_reel1, int caps_in_reel2)
+{
+       boost::filesystem::remove_all (dir);
+       boost::filesystem::create_directories (dir);
+       auto dcp = make_shared<dcp::DCP>(dir);
+       auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::FEATURE);
+
+       auto subs = make_shared<dcp::SMPTESubtitleAsset>();
+       subs->set_language (dcp::LanguageTag("de-DE"));
+       subs->set_start_time (dcp::Time());
+       subs->add (simple_subtitle());
+       subs->write (dir / "subs.mxf");
+
+       auto reel1 = make_shared<dcp::Reel>(
+               make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
+               make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
+               );
+
+       for (int i = 0; i < caps_in_reel1; ++i) {
+               reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
+       }
+
+       cpl->add (reel1);
+
+       auto reel2 = make_shared<dcp::Reel>(
+               make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
+               make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
+               );
+
+       for (int i = 0; i < caps_in_reel2; ++i) {
+               reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
+       }
+
+       cpl->add (reel2);
+
+       dcp->add (cpl);
+       dcp->write_xml (dcp::SMPTE);
+
+}
+
+
+BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels)
+{
+       {
+               boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels1");
+               verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
+               check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_ASSET_COUNTS_DIFFER }});
+       }
+
+       {
+               boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
+               verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
+               auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+               BOOST_REQUIRE (notes.empty());
+       }
+
+       {
+               boost::filesystem::path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
+               verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
+               auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
+               BOOST_REQUIRE (notes.empty());
+       }
+}
+