X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fverify.cc;h=c9d9b24d04e6081663490b77f6d67de1011c836c;hb=ba27603d5b53231607bc8fe41b201d8811b22b4f;hp=9cfcd4b9c38657bad4fb6a0562cb3844db3aa902;hpb=9a5db0824d48bac475abbb9ff4dc1c7b5f28edab;p=libdcp.git diff --git a/src/verify.cc b/src/verify.cc index 9cfcd4b9..c9d9b24d 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2018-2020 Carl Hetherington + Copyright (C) 2018-2021 Carl Hetherington This file is part of libdcp. @@ -31,48 +31,56 @@ files in the program, then also delete it here. */ -#include "verify.h" -#include "dcp.h" + +/** @file src/verify.cc + * @brief dcp::verify() method and associated code + */ + + +#include "compose.hpp" #include "cpl.h" +#include "dcp.h" +#include "exceptions.h" +#include "interop_subtitle_asset.h" +#include "mono_picture_asset.h" +#include "mono_picture_frame.h" +#include "raw_convert.h" #include "reel.h" #include "reel_closed_caption_asset.h" +#include "reel_interop_subtitle_asset.h" +#include "reel_markers_asset.h" #include "reel_picture_asset.h" #include "reel_sound_asset.h" +#include "reel_smpte_subtitle_asset.h" #include "reel_subtitle_asset.h" -#include "interop_subtitle_asset.h" -#include "mono_picture_asset.h" -#include "mono_picture_frame.h" +#include "smpte_subtitle_asset.h" #include "stereo_picture_asset.h" #include "stereo_picture_frame.h" -#include "exceptions.h" -#include "compose.hpp" -#include "raw_convert.h" -#include "smpte_subtitle_asset.h" -#include -#include -#include -#include +#include "verify.h" +#include "verify_j2k.h" +#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include -#include #include #include -#include -#include +#include #include #include -#include -#include +#include +#include +#include +#include #include +#include #include -#include #include -#include + using std::list; using std::vector; @@ -80,14 +88,18 @@ using std::string; using std::cout; using std::map; using std::max; +using std::set; using std::shared_ptr; +using std::make_shared; using boost::optional; using boost::function; using std::dynamic_pointer_cast; + using namespace dcp; using namespace xercesc; + static string xml_ch_to_string (XMLCh const * a) @@ -98,6 +110,7 @@ xml_ch_to_string (XMLCh const * a) return o; } + class XMLValidationError { public: @@ -143,22 +156,22 @@ private: class DCPErrorHandler : public ErrorHandler { public: - void warning(const SAXParseException& e) + void warning(const SAXParseException& e) override { maybe_add (XMLValidationError(e)); } - void error(const SAXParseException& e) + void error(const SAXParseException& e) override { maybe_add (XMLValidationError(e)); } - void fatalError(const SAXParseException& e) + void fatalError(const SAXParseException& e) override { maybe_add (XMLValidationError(e)); } - void resetErrors() { + void resetErrors() override { _errors.clear (); } @@ -183,7 +196,8 @@ private: list _errors; }; -class StringToXMLCh : public boost::noncopyable + +class StringToXMLCh { public: StringToXMLCh (string a) @@ -191,6 +205,9 @@ public: _buffer = XMLString::transcode(a.c_str()); } + StringToXMLCh (StringToXMLCh const&) = delete; + StringToXMLCh& operator= (StringToXMLCh const&) = delete; + ~StringToXMLCh () { XMLString::release (&_buffer); @@ -204,6 +221,7 @@ private: XMLCh* _buffer; }; + class LocalFileResolver : public EntityResolver { public: @@ -228,13 +246,13 @@ public: add("http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL", "SMPTE-429-10-2008.xsd"); } - InputSource* resolveEntity(XMLCh const *, XMLCh const * system_id) + InputSource* resolveEntity(XMLCh const *, XMLCh const * system_id) override { if (!system_id) { return 0; } - string system_id_str = xml_ch_to_string (system_id); - boost::filesystem::path p = _xsd_dtd_directory; + auto system_id_str = xml_ch_to_string (system_id); + auto p = _xsd_dtd_directory; if (_files.find(system_id_str) == _files.end()) { p /= system_id_str; } else { @@ -263,7 +281,7 @@ parse (XercesDOMParser& parser, boost::filesystem::path xml) static void -parse (XercesDOMParser& parser, std::string xml) +parse (XercesDOMParser& parser, string xml) { xercesc::MemBufInputSource buf(reinterpret_cast(xml.c_str()), xml.size(), ""); parser.parse(buf); @@ -272,7 +290,7 @@ parse (XercesDOMParser& parser, std::string xml) template void -validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, list& notes) +validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector& notes) { try { XMLPlatformUtils::Initialize (); @@ -316,7 +334,7 @@ validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, list dcp, shared_ptr reel_mxf, function progress) +verify_asset (shared_ptr dcp, shared_ptr reel_file_asset, function progress) { - string const actual_hash = reel_mxf->asset_ref()->hash(progress); + auto const actual_hash = reel_file_asset->asset_ref()->hash(progress); - list > pkls = dcp->pkls(); + auto pkls = dcp->pkls(); /* We've read this DCP in so it must have at least one PKL */ DCP_ASSERT (!pkls.empty()); - shared_ptr asset = reel_mxf->asset_ref().asset(); + auto asset = reel_file_asset->asset_ref().asset(); optional pkl_hash; - BOOST_FOREACH (shared_ptr i, pkls) { - pkl_hash = i->hash (reel_mxf->asset_ref()->id()); + for (auto i: pkls) { + pkl_hash = i->hash (reel_file_asset->asset_ref()->id()); if (pkl_hash) { break; } @@ -383,91 +399,84 @@ verify_asset (shared_ptr dcp, shared_ptr reel_mxf, fun DCP_ASSERT (pkl_hash); - optional cpl_hash = reel_mxf->hash(); + auto cpl_hash = reel_file_asset->hash(); if (cpl_hash && *cpl_hash != *pkl_hash) { - return VERIFY_ASSET_RESULT_CPL_PKL_DIFFER; + return VerifyAssetResult::CPL_PKL_DIFFER; } if (actual_hash != *pkl_hash) { - return VERIFY_ASSET_RESULT_BAD; + return VerifyAssetResult::BAD; } - return VERIFY_ASSET_RESULT_GOOD; + return VerifyAssetResult::GOOD; } void -verify_language_tag (string tag, list& notes) +verify_language_tag (string tag, vector& notes) { try { - dcp::LanguageTag test (tag); - } catch (dcp::LanguageTagError &) { - notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::BAD_LANGUAGE, tag)); + LanguageTag test (tag); + } catch (LanguageTagError &) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_LANGUAGE, tag}); } } -enum VerifyPictureAssetResult -{ - VERIFY_PICTURE_ASSET_RESULT_GOOD, - VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE, - VERIFY_PICTURE_ASSET_RESULT_BAD, -}; - - -int -biggest_frame_size (shared_ptr frame) -{ - return frame->size (); -} - -int -biggest_frame_size (shared_ptr frame) +static void +verify_picture_asset (shared_ptr reel_file_asset, boost::filesystem::path file, vector& notes, function progress) { - return max(frame->left()->size(), frame->right()->size()); -} - + int biggest_frame = 0; + auto asset = dynamic_pointer_cast(reel_file_asset->asset_ref().asset()); + auto const duration = asset->intrinsic_duration (); -template -optional -verify_picture_asset_type (shared_ptr reel_mxf, function progress) -{ - shared_ptr asset = dynamic_pointer_cast(reel_mxf->asset_ref().asset()); - if (!asset) { - return optional(); - } + auto check_and_add = [¬es](vector const& j2k_notes) { + for (auto i: j2k_notes) { + if (find(notes.begin(), notes.end(), i) == notes.end()) { + notes.push_back (i); + } + } + }; + + if (auto mono_asset = dynamic_pointer_cast(reel_file_asset->asset_ref().asset())) { + auto reader = mono_asset->start_read (); + for (int64_t i = 0; i < duration; ++i) { + auto frame = reader->get_frame (i); + biggest_frame = max(biggest_frame, frame->size()); + if (!mono_asset->encrypted() || mono_asset->key()) { + vector j2k_notes; + verify_j2k (frame, j2k_notes); + check_and_add (j2k_notes); + } + progress (float(i) / duration); + } + } else if (auto stereo_asset = dynamic_pointer_cast(asset)) { + auto reader = stereo_asset->start_read (); + for (int64_t i = 0; i < duration; ++i) { + auto frame = reader->get_frame (i); + biggest_frame = max(biggest_frame, max(frame->left()->size(), frame->right()->size())); + if (!stereo_asset->encrypted() || mono_asset->key()) { + vector j2k_notes; + verify_j2k (frame->left(), j2k_notes); + verify_j2k (frame->right(), j2k_notes); + check_and_add (j2k_notes); + } + progress (float(i) / duration); + } - int biggest_frame = 0; - shared_ptr reader = asset->start_read (); - int64_t const duration = asset->intrinsic_duration (); - for (int64_t i = 0; i < duration; ++i) { - shared_ptr frame = reader->get_frame (i); - biggest_frame = max(biggest_frame, biggest_frame_size(frame)); - progress (float(i) / duration); } static const int max_frame = rint(250 * 1000000 / (8 * asset->edit_rate().as_float())); static const int risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float())); if (biggest_frame > max_frame) { - return VERIFY_PICTURE_ASSET_RESULT_BAD; + notes.push_back ({ + VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file + }); } else if (biggest_frame > risky_frame) { - return VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE; - } - - return VERIFY_PICTURE_ASSET_RESULT_GOOD; -} - - -static VerifyPictureAssetResult -verify_picture_asset (shared_ptr reel_mxf, function progress) -{ - optional r = verify_picture_asset_type(reel_mxf, progress); - if (!r) { - r = verify_picture_asset_type(reel_mxf, progress); + notes.push_back ({ + VerificationNote::Type::WARNING, VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file + }); } - - DCP_ASSERT (r); - return *r; } @@ -477,105 +486,76 @@ verify_main_picture_asset ( shared_ptr reel_asset, function)> stage, function progress, - list& notes + vector& notes ) { - shared_ptr asset = reel_asset->asset(); - boost::filesystem::path const file = *asset->file(); + auto asset = reel_asset->asset(); + auto const file = *asset->file(); stage ("Checking picture asset hash", file); - VerifyAssetResult const r = verify_asset (dcp, reel_asset, progress); + auto const r = verify_asset (dcp, reel_asset, progress); switch (r) { - case VERIFY_ASSET_RESULT_BAD: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file - ) - ); + case VerifyAssetResult::BAD: + notes.push_back ({ + VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_PICTURE_HASH, file + }); break; - case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file - ) - ); + case VerifyAssetResult::CPL_PKL_DIFFER: + notes.push_back ({ + VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_PICTURE_HASHES, file + }); break; default: break; } stage ("Checking picture frame sizes", asset->file()); - VerifyPictureAssetResult const pr = verify_picture_asset (reel_asset, progress); - switch (pr) { - case VERIFY_PICTURE_ASSET_RESULT_BAD: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES, file - ) - ); - break; - case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES, file - ) - ); - break; - default: - break; - } + verify_picture_asset (reel_asset, file, notes, progress); /* Only flat/scope allowed by Bv2.1 */ if ( - asset->size() != dcp::Size(2048, 858) && - asset->size() != dcp::Size(1998, 1080) && - asset->size() != dcp::Size(4096, 1716) && - asset->size() != dcp::Size(3996, 2160)) { - notes.push_back( - VerificationNote( - VerificationNote::VERIFY_BV21_ERROR, - VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS, - String::compose("%1x%2", asset->size().width, asset->size().height), - file - ) - ); + asset->size() != Size(2048, 858) && + asset->size() != Size(1998, 1080) && + asset->size() != Size(4096, 1716) && + asset->size() != Size(3996, 2160)) { + notes.push_back({ + VerificationNote::Type::BV21_ERROR, + VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS, + String::compose("%1x%2", asset->size().width, asset->size().height), + file + }); } /* Only 24, 25, 48fps allowed for 2K */ if ( - (asset->size() == dcp::Size(2048, 858) || asset->size() == dcp::Size(1998, 1080)) && - (asset->edit_rate() != dcp::Fraction(24, 1) && asset->edit_rate() != dcp::Fraction(25, 1) && asset->edit_rate() != dcp::Fraction(48, 1)) + (asset->size() == Size(2048, 858) || asset->size() == Size(1998, 1080)) && + (asset->edit_rate() != Fraction(24, 1) && asset->edit_rate() != Fraction(25, 1) && asset->edit_rate() != Fraction(48, 1)) ) { - notes.push_back( - VerificationNote( - VerificationNote::VERIFY_BV21_ERROR, - VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K, - String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), - file - ) - ); + notes.push_back({ + VerificationNote::Type::BV21_ERROR, + VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K, + String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), + file + }); } - if (asset->size() == dcp::Size(4096, 1716) || asset->size() == dcp::Size(3996, 2160)) { + if (asset->size() == Size(4096, 1716) || asset->size() == Size(3996, 2160)) { /* Only 24fps allowed for 4K */ - if (asset->edit_rate() != dcp::Fraction(24, 1)) { - notes.push_back( - VerificationNote( - VerificationNote::VERIFY_BV21_ERROR, - VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K, - String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), - file - ) - ); + if (asset->edit_rate() != Fraction(24, 1)) { + notes.push_back({ + VerificationNote::Type::BV21_ERROR, + VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K, + String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), + file + }); } /* Only 2D allowed for 4K */ if (dynamic_pointer_cast(asset)) { - notes.push_back( - VerificationNote( - VerificationNote::VERIFY_BV21_ERROR, - VerificationNote::PICTURE_ASSET_4K_3D, - file - ) - ); + notes.push_back({ + VerificationNote::Type::BV21_ERROR, + VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D, + String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), + file + }); } } @@ -589,26 +569,18 @@ verify_main_sound_asset ( shared_ptr reel_asset, function)> stage, function progress, - list& notes + vector& notes ) { - shared_ptr asset = reel_asset->asset(); + auto asset = reel_asset->asset(); stage ("Checking sound asset hash", asset->file()); - VerifyAssetResult const r = verify_asset (dcp, reel_asset, progress); + auto const r = verify_asset (dcp, reel_asset, progress); switch (r) { - case VERIFY_ASSET_RESULT_BAD: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *asset->file() - ) - ); + case VerifyAssetResult::BAD: + notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_SOUND_HASH, *asset->file()}); break; - case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER: - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE, *asset->file() - ) - ); + case VerifyAssetResult::CPL_PKL_DIFFER: + notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_SOUND_HASHES, *asset->file()}); break; default: break; @@ -616,36 +588,140 @@ verify_main_sound_asset ( stage ("Checking sound asset metadata", asset->file()); - verify_language_tag (asset->language(), notes); + if (auto lang = asset->language()) { + verify_language_tag (*lang, notes); + } + if (asset->sampling_rate() != 48000) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_SOUND_FRAME_RATE, raw_convert(asset->sampling_rate()), *asset->file()}); + } } static void -verify_main_subtitle_reel (shared_ptr reel_asset, list& notes) +verify_main_subtitle_reel (shared_ptr reel_asset, vector& notes) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { verify_language_tag (*reel_asset->language(), notes); } + + if (!reel_asset->entry_point()) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, reel_asset->id() }); + } else if (reel_asset->entry_point().get()) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, reel_asset->id() }); + } } static void -verify_closed_caption_reel (shared_ptr reel_asset, list& notes) +verify_closed_caption_reel (shared_ptr reel_asset, vector& notes) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { verify_language_tag (*reel_asset->language(), notes); } + + if (!reel_asset->entry_point()) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id() }); + } else if (reel_asset->entry_point().get()) { + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id() }); + } } +struct State +{ + boost::optional subtitle_language; +}; + + +/** Verify stuff that is common to both subtitles and closed captions */ +void +verify_smpte_timed_text_asset ( + shared_ptr asset, + optional reel_asset_duration, + vector& notes + ) +{ + if (asset->language()) { + verify_language_tag (*asset->language(), notes); + } else { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file() }); + } + + auto const size = boost::filesystem::file_size(asset->file().get()); + if (size > 115 * 1024 * 1024) { + notes.push_back ( + { VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, raw_convert(size), *asset->file() } + ); + } + + /* XXX: I'm not sure what Bv2.1_7.2.1 means when it says "the font resource shall not be larger than 10MB" + * but I'm hoping that checking for the total size of all fonts being <= 10MB will do. + */ + auto fonts = asset->font_data (); + int total_size = 0; + for (auto i: fonts) { + total_size += i.second.size(); + } + if (total_size > 10 * 1024 * 1024) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, raw_convert(total_size), asset->file().get() }); + } + + if (!asset->start_time()) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_START_TIME, asset->file().get() }); + } else if (asset->start_time() != Time()) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_SUBTITLE_START_TIME, asset->file().get() }); + } + + if (reel_asset_duration && *reel_asset_duration != asset->intrinsic_duration()) { + notes.push_back ( + { + VerificationNote::Type::BV21_ERROR, + VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION, + String::compose("%1 %2", *reel_asset_duration, asset->intrinsic_duration()), + asset->file().get() + }); + } +} + + +/** Verify SMPTE subtitle-only stuff */ +void +verify_smpte_subtitle_asset ( + shared_ptr asset, + vector& notes, + State& state + ) +{ + if (asset->language()) { + if (!state.subtitle_language) { + state.subtitle_language = *asset->language(); + } else if (state.subtitle_language != *asset->language()) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }); + } + } + + DCP_ASSERT (asset->resource_id()); + if (asset->resource_id().get() != asset->xml_id()) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID }); + } + + if (asset->id() == asset->resource_id().get() || asset->id() == asset->xml_id()) { + notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID }); + } +} + + +/** Verify all subtitle stuff */ static void verify_subtitle_asset ( shared_ptr asset, + optional reel_asset_duration, function)> stage, boost::filesystem::path xsd_dtd_directory, - list& notes + vector& notes, + State& state ) { stage ("Checking subtitle XML", asset->file()); @@ -654,105 +730,533 @@ verify_subtitle_asset ( */ validate_xml (asset->raw_xml(), xsd_dtd_directory, notes); - shared_ptr smpte = dynamic_pointer_cast(asset); + auto smpte = dynamic_pointer_cast(asset); if (smpte) { - if (smpte->language()) { - verify_language_tag (*smpte->language(), notes); - } + verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); + verify_smpte_subtitle_asset (smpte, notes, state); } } +/** Verify all closed caption stuff */ static void verify_closed_caption_asset ( shared_ptr asset, + optional reel_asset_duration, function)> stage, boost::filesystem::path xsd_dtd_directory, - list& notes + vector& notes ) { - verify_subtitle_asset (asset, stage, xsd_dtd_directory, notes); + stage ("Checking closed caption XML", asset->file()); + /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk + * gets passed through libdcp which may clean up and therefore hide errors. + */ + validate_xml (asset->raw_xml(), xsd_dtd_directory, notes); + + auto smpte = dynamic_pointer_cast(asset); + if (smpte) { + verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); + } if (asset->raw_xml().size() > 256 * 1024) { - notes.push_back ( - VerificationNote( - VerificationNote::VERIFY_BV21_ERROR, VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES, *asset->file() - ) - ); + notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert(asset->raw_xml().size()), *asset->file()}); + } +} + + +static +void +verify_text_timing ( + vector> reels, + int edit_rate, + vector& notes, + std::function)> check, + std::function)> xml, + std::function)> duration + ) +{ + /* end of last subtitle (in editable units) */ + optional last_out; + auto too_short = false; + auto too_close = false; + auto too_early = false; + auto reel_overlap = false; + /* current reel start time (in editable units) */ + int64_t reel_offset = 0; + + std::function, optional