Add check for Interop font assets being present (in the ASSETMAP and on disk).
authorCarl Hetherington <cth@carlh.net>
Thu, 13 Apr 2023 21:39:40 +0000 (23:39 +0200)
committerCarl Hetherington <cth@carlh.net>
Thu, 13 Apr 2023 21:39:40 +0000 (23:39 +0200)
src/interop_subtitle_asset.cc
src/interop_subtitle_asset.h
src/verify.cc
src/verify.h
test/verify_test.cc
tools/common.cc

index b815da555e411064f48f6997f73e513564e7b2b7..24cd907da00ccb9d42ebebb067840b32e339c6c4 100644 (file)
@@ -309,3 +309,16 @@ InteropSubtitleAsset::set_font_file (string load_id, boost::filesystem::path fil
        }
 }
 
+
+vector<string>
+InteropSubtitleAsset::unresolved_fonts() const
+{
+       vector<string> unresolved;
+       for (auto load_font_node: _load_font_nodes) {
+               if (std::find_if(_fonts.begin(), _fonts.end(), [load_font_node](Font const& font) { return font.load_id == load_font_node->id; }) == _fonts.end()) {
+                       unresolved.push_back(load_font_node->id);
+               }
+       }
+       return unresolved;
+}
+
index 23cf0b492fc51aac6b06baf83ecab7016bbc3fb3..467728dbcba8c46cae3a815fcf50e0d73155af85 100644 (file)
@@ -85,6 +85,12 @@ public:
        void add_font_assets (std::vector<std::shared_ptr<Asset>>& assets);
        void set_font_file (std::string load_id, boost::filesystem::path file);
 
+       /** @return the <LoadFont> IDs of fonts for which we have not (yet) found a font asset.
+        *  This could be because resolve_fonts() has not yet been called, or because there is
+        *  a missing font file.
+        */
+       std::vector<std::string> unresolved_fonts() const;
+
        /** Set the reel number or sub-element identifier
         *  of these subtitles.
         *  @param n New reel number.
index ab66a00eea783dcd6def144a1870f305a6c6987c..d43c7ff13abe88db51beb894c5b769c3e81ec6ec 100644 (file)
@@ -715,6 +715,10 @@ verify_interop_subtitle_asset(shared_ptr<const InteropSubtitleAsset> asset, vect
        if (asset->subtitles().empty()) {
                notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_SUBTITLE, asset->id(), asset->file().get() });
        }
+       auto const unresolved = asset->unresolved_fonts();
+       if (!unresolved.empty()) {
+               notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_FONT, unresolved.front() });
+       }
 }
 
 
@@ -2008,6 +2012,8 @@ dcp::note_to_string (VerificationNote note)
                return String::compose("The sound assets do not all have the same channel count; the first to differ is %1", note.file()->filename());
        case VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION:
                return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get());
+       case VerificationNote::Code::MISSING_FONT:
+               return String::compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get());
        }
 
        return "";
index 7696ea851151ab2bf5e32b6c53a07335e9b4e1e8..ed5cac4c05ec8ef89d8b5c339051935dfbef439c 100644 (file)
@@ -432,6 +432,10 @@ public:
                 *  file contains the CPL filename
                 */
                INVALID_MAIN_SOUND_CONFIGURATION,
+               /** An interop subtitle file has a <LoadFont> node which refers to a font file that is not found.
+                *  note contains the <LoadFont> ID
+                */
+               MISSING_FONT
        };
 
        VerificationNote (Type type, Code code)
index 159c179c1867625723e8c83dcd88f54f239775ea..62e386b796de5421378302ad81601e39e7bf4053 100644 (file)
@@ -770,7 +770,11 @@ BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
        auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
        write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
 
-       check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
+       check_verify_result (
+               {dir}, {
+                       { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
+               });
 }
 
 
@@ -801,7 +805,8 @@ BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
                                string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
                                path(),
                                29
-                       }
+                       },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
                });
 }
 
@@ -820,6 +825,7 @@ BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
                {
                        { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
                        { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
                });
 
 }
@@ -838,6 +844,7 @@ BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
                { dir },
                {
                        { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
                });
 
 }
@@ -926,6 +933,7 @@ BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
                { dir },
                {
                        { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
                });
 }
 
@@ -946,6 +954,7 @@ BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes
                        { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
                        { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
                        { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
+                       { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
                });
 }
 
index 13ce9cb0f9092eaff20720095509c2fa0ebe2bd1..030a2ce77aba0666d91144494ab6451bd35f3b76 100644 (file)
@@ -49,7 +49,9 @@ dcp::filter_notes (vector<dcp::VerificationNote>& notes, bool ignore_missing_ass
 
        vector<dcp::VerificationNote> filtered;
        std::copy_if (notes.begin(), notes.end(), std::back_inserter(filtered), [](dcp::VerificationNote const& i) {
-               return i.code() != dcp::VerificationNote::Code::MISSING_ASSET && i.code() != dcp::VerificationNote::Code::EXTERNAL_ASSET;
+               return i.code() != dcp::VerificationNote::Code::MISSING_ASSET &&
+                       i.code() != dcp::VerificationNote::Code::EXTERNAL_ASSET &&
+                       i.code() != dcp::VerificationNote::Code::MISSING_FONT;
        });
 
        notes = filtered;