diff options
Diffstat (limited to 'src/verify.cc')
| -rw-r--r-- | src/verify.cc | 960 |
1 files changed, 499 insertions, 461 deletions
diff --git a/src/verify.cc b/src/verify.cc index ec8925f2..1e27514a 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -43,8 +43,8 @@ #include "exceptions.h" #include "filesystem.h" #include "interop_subtitle_asset.h" -#include "mono_picture_asset.h" -#include "mono_picture_frame.h" +#include "mono_j2k_picture_asset.h" +#include "mono_j2k_picture_frame.h" #include "raw_convert.h" #include "reel.h" #include "reel_closed_caption_asset.h" @@ -55,8 +55,8 @@ #include "reel_smpte_subtitle_asset.h" #include "reel_subtitle_asset.h" #include "smpte_subtitle_asset.h" -#include "stereo_picture_asset.h" -#include "stereo_picture_frame.h" +#include "stereo_j2k_picture_asset.h" +#include "stereo_j2k_picture_frame.h" #include "verify.h" #include "verify_j2k.h" #include <libxml/parserInternals.h> @@ -89,6 +89,7 @@ using std::cout; using std::dynamic_pointer_cast; +using std::function; using std::list; using std::make_shared; using std::map; @@ -98,7 +99,6 @@ using std::shared_ptr; using std::string; using std::vector; using boost::optional; -using boost::function; using namespace dcp; @@ -294,9 +294,83 @@ parse (XercesDOMParser& parser, string xml) } +class Context +{ +public: + Context( + std::vector<VerificationNote>& notes_, + boost::filesystem::path xsd_dtd_directory_, + function<void (string, optional<boost::filesystem::path>)> stage_, + function<void (float)> progress_, + VerificationOptions options_ + ) + : notes(notes_) + , xsd_dtd_directory(xsd_dtd_directory_) + , stage(stage_) + , progress(progress_) + , options(options_) + { + + } + + Context(Context const&) = delete; + Context& operator=(Context const&) = delete; + + template<typename... Args> + void ok(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::OK, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void warning(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::WARNING, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void bv21_error(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::BV21_ERROR, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void error(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::ERROR, code, std::forward<Args>(args)...}); + } + + void add_note(dcp::VerificationNote note) + { + if (cpl) { + note.set_cpl_id(cpl->id()); + } + notes.push_back(std::move(note)); + } + + void add_note_if_not_existing(dcp::VerificationNote note) + { + if (find(notes.begin(), notes.end(), note) == notes.end()) { + add_note(note); + } + } + + std::vector<VerificationNote>& notes; + std::shared_ptr<const DCP> dcp; + std::shared_ptr<const CPL> cpl; + boost::filesystem::path xsd_dtd_directory; + function<void (string, optional<boost::filesystem::path>)> stage; + function<void (float)> progress; + VerificationOptions options; + + boost::optional<string> subtitle_language; + boost::optional<int> audio_channels; +}; + + template <class T> void -validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<VerificationNote>& notes) +validate_xml(Context& context, T xml) { try { XMLPlatformUtils::Initialize (); @@ -349,7 +423,7 @@ validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<Verificat parser.setValidationSchemaFullChecking(true); parser.setErrorHandler(&error_handler); - LocalFileResolver resolver (xsd_dtd_directory); + LocalFileResolver resolver(context.xsd_dtd_directory); parser.setEntityResolver(&resolver); try { @@ -367,13 +441,12 @@ validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<Verificat XMLPlatformUtils::Terminate (); for (auto i: error_handler.errors()) { - notes.push_back ({ - VerificationNote::Type::ERROR, + context.error( VerificationNote::Code::INVALID_XML, i.message(), boost::trim_copy(i.public_id() + " " + i.system_id()), i.line() - }); + ); } } @@ -387,9 +460,8 @@ enum class VerifyAssetResult { static VerifyAssetResult verify_asset( - shared_ptr<const DCP> dcp, + Context& context, shared_ptr<const ReelFileAsset> reel_file_asset, - function<void (float)> progress, string* reference_hash, string* calculated_hash ) @@ -403,11 +475,11 @@ verify_asset( * call to hash(). */ reel_file_asset->asset_ref()->unset_hash(); - *calculated_hash = reel_file_asset->asset_ref()->hash([progress](int64_t done, int64_t total) { - progress(float(done) / total); + *calculated_hash = reel_file_asset->asset_ref()->hash([&context](int64_t done, int64_t total) { + context.progress(float(done) / total); }); - auto pkls = dcp->pkls(); + auto pkls = context.dcp->pkls(); /* We've read this DCP in so it must have at least one PKL */ DCP_ASSERT (!pkls.empty()); @@ -437,103 +509,106 @@ verify_asset( } -void -verify_language_tag (string tag, vector<VerificationNote>& notes) +static void +verify_language_tag(Context& context, string tag) { try { LanguageTag test (tag); } catch (LanguageTagError &) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_LANGUAGE, tag}); + context.bv21_error(VerificationNote::Code::INVALID_LANGUAGE, tag); } } static void -verify_picture_asset(shared_ptr<const ReelFileAsset> reel_file_asset, boost::filesystem::path file, int64_t start_frame, vector<VerificationNote>& notes, function<void (float)> progress) +verify_picture_asset( + Context& context, + shared_ptr<const ReelFileAsset> reel_file_asset, + boost::filesystem::path file, + int64_t start_frame + ) { - auto asset = dynamic_pointer_cast<PictureAsset>(reel_file_asset->asset_ref().asset()); + auto asset = dynamic_pointer_cast<J2KPictureAsset>(reel_file_asset->asset_ref().asset()); auto const duration = asset->intrinsic_duration (); - auto check_and_add = [¬es](vector<VerificationNote> const& j2k_notes) { + auto check_and_add = [&context](vector<VerificationNote> const& j2k_notes) { for (auto i: j2k_notes) { - if (find(notes.begin(), notes.end(), i) == notes.end()) { - notes.push_back (i); - } + context.add_note_if_not_existing(i); } }; int const max_frame = rint(250 * 1000000 / (8 * asset->edit_rate().as_float())); int const risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float())); - auto check_frame_size = [max_frame, risky_frame, file, start_frame](int index, int size, int frame_rate, vector<VerificationNote>& notes) { + bool any_bad_frames_seen = false; + + auto check_frame_size = [max_frame, risky_frame, file, start_frame, &any_bad_frames_seen](Context& context, int index, int size, int frame_rate) { if (size > max_frame) { - notes.push_back( + context.add_note( VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file ).set_frame(start_frame + index).set_frame_rate(frame_rate) ); + any_bad_frames_seen = true; } else if (size > risky_frame) { - notes.push_back( + context.add_note( VerificationNote( VerificationNote::Type::WARNING, VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file ).set_frame(start_frame + index).set_frame_rate(frame_rate) ); + any_bad_frames_seen = true; } }; - if (auto mono_asset = dynamic_pointer_cast<MonoPictureAsset>(reel_file_asset->asset_ref().asset())) { + if (auto mono_asset = dynamic_pointer_cast<MonoJ2KPictureAsset>(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); - check_frame_size(i, frame->size(), mono_asset->frame_rate().numerator, notes); + check_frame_size(context, i, frame->size(), mono_asset->frame_rate().numerator); if (!mono_asset->encrypted() || mono_asset->key()) { vector<VerificationNote> j2k_notes; verify_j2k(frame, start_frame, i, mono_asset->frame_rate().numerator, j2k_notes); check_and_add (j2k_notes); } - progress (float(i) / duration); + context.progress(float(i) / duration); } - } else if (auto stereo_asset = dynamic_pointer_cast<StereoPictureAsset>(asset)) { + } else if (auto stereo_asset = dynamic_pointer_cast<StereoJ2KPictureAsset>(asset)) { auto reader = stereo_asset->start_read (); for (int64_t i = 0; i < duration; ++i) { auto frame = reader->get_frame (i); - check_frame_size(i, frame->left()->size(), stereo_asset->frame_rate().numerator, notes); - check_frame_size(i, frame->right()->size(), stereo_asset->frame_rate().numerator, notes); + check_frame_size(context, i, frame->left()->size(), stereo_asset->frame_rate().numerator); + check_frame_size(context, i, frame->right()->size(), stereo_asset->frame_rate().numerator); if (!stereo_asset->encrypted() || stereo_asset->key()) { vector<VerificationNote> j2k_notes; verify_j2k(frame->left(), start_frame, i, stereo_asset->frame_rate().numerator, j2k_notes); verify_j2k(frame->right(), start_frame, i, stereo_asset->frame_rate().numerator, j2k_notes); check_and_add (j2k_notes); } - progress (float(i) / duration); + context.progress(float(i) / duration); } } + + if (!any_bad_frames_seen) { + context.ok(VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, file); + } } static void -verify_main_picture_asset ( - shared_ptr<const DCP> dcp, - shared_ptr<const ReelPictureAsset> reel_asset, - int64_t start_frame, - function<void (string, optional<boost::filesystem::path>)> stage, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes - ) +verify_main_picture_asset(Context& context, shared_ptr<const ReelPictureAsset> reel_asset, int64_t start_frame) { auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { - stage ("Checking picture asset hash", file); + if (context.options.check_asset_hashes && (!context.options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *context.options.maximum_asset_size_for_hash_check)) { + context.stage("Checking picture asset hash", file); string reference_hash; string calculated_hash; - auto const r = verify_asset(dcp, reel_asset, progress, &reference_hash, &calculated_hash); + auto const r = verify_asset(context, reel_asset, &reference_hash, &calculated_hash); switch (r) { case VerifyAssetResult::BAD: - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_PICTURE_HASH, @@ -542,17 +617,16 @@ verify_main_picture_asset ( ); break; case VerifyAssetResult::CPL_PKL_DIFFER: - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_PICTURE_HASHES, file - }); + context.error(VerificationNote::Code::MISMATCHED_PICTURE_HASHES, file); break; default: + context.ok(VerificationNote::Code::CORRECT_PICTURE_HASH, file); break; } } - stage ("Checking picture frame sizes", asset->file()); - verify_picture_asset(reel_asset, file, start_frame, notes, progress); + context.stage("Checking picture frame sizes", asset->file()); + verify_picture_asset(context, reel_asset, file, start_frame); /* Only flat/scope allowed by Bv2.1 */ if ( @@ -560,12 +634,7 @@ verify_main_picture_asset ( 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 - }); + context.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 */ @@ -573,69 +642,50 @@ verify_main_picture_asset ( (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::Type::BV21_ERROR, + context.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() == Size(4096, 1716) || asset->size() == Size(3996, 2160)) { /* Only 24fps allowed for 4K */ if (asset->edit_rate() != Fraction(24, 1)) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, + context.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<const StereoPictureAsset>(asset)) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, + if (dynamic_pointer_cast<const StereoJ2KPictureAsset>(asset)) { + context.bv21_error( VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D, String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), file - }); + ); } } - } -struct State -{ - boost::optional<string> subtitle_language; - boost::optional<int> audio_channels; -}; - - static void -verify_main_sound_asset ( - shared_ptr<const DCP> dcp, - shared_ptr<const ReelSoundAsset> reel_asset, - function<void (string, optional<boost::filesystem::path>)> stage, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state - ) +verify_main_sound_asset(Context& context, shared_ptr<const ReelSoundAsset> reel_asset) { auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { - stage("Checking sound asset hash", file); + if (context.options.check_asset_hashes && (!context.options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *context.options.maximum_asset_size_for_hash_check)) { + context.stage("Checking sound asset hash", file); string reference_hash; string calculated_hash; - auto const r = verify_asset(dcp, reel_asset, progress, &reference_hash, &calculated_hash); + auto const r = verify_asset(context, reel_asset, &reference_hash, &calculated_hash); switch (r) { case VerifyAssetResult::BAD: - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_SOUND_HASH, @@ -644,58 +694,61 @@ verify_main_sound_asset ( ); break; case VerifyAssetResult::CPL_PKL_DIFFER: - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_SOUND_HASHES, file}); + context.error(VerificationNote::Code::MISMATCHED_SOUND_HASHES, file); break; default: break; } } - if (!state.audio_channels) { - state.audio_channels = asset->channels(); - } else if (*state.audio_channels != asset->channels()) { - notes.push_back({ VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, file }); + if (!context.audio_channels) { + context.audio_channels = asset->channels(); + } else if (*context.audio_channels != asset->channels()) { + context.error(VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, file); } - stage ("Checking sound asset metadata", file); + context.stage("Checking sound asset metadata", file); if (auto lang = asset->language()) { - verify_language_tag (*lang, notes); + verify_language_tag(context, *lang); } if (asset->sampling_rate() != 48000) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_SOUND_FRAME_RATE, raw_convert<string>(asset->sampling_rate()), file}); + context.bv21_error(VerificationNote::Code::INVALID_SOUND_FRAME_RATE, raw_convert<string>(asset->sampling_rate()), file); + } + if (asset->bit_depth() != 24) { + context.error(VerificationNote::Code::INVALID_SOUND_BIT_DEPTH, raw_convert<string>(asset->bit_depth()), file); } } static void -verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, vector<VerificationNote>& notes) +verify_main_subtitle_reel(Context& context, shared_ptr<const ReelSubtitleAsset> reel_asset) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { - verify_language_tag (*reel_asset->language(), notes); + verify_language_tag(context, *reel_asset->language()); } if (!reel_asset->entry_point()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, reel_asset->id() }); + context.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() }); + context.bv21_error(VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, reel_asset->id()); } } static void -verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset, vector<VerificationNote>& notes) +verify_closed_caption_reel(Context& context, shared_ptr<const ReelClosedCaptionAsset> reel_asset) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { - verify_language_tag (*reel_asset->language(), notes); + verify_language_tag(context, *reel_asset->language()); } if (!reel_asset->entry_point()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id() }); + context.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() }); + context.bv21_error(VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id()); } } @@ -703,22 +756,20 @@ verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset, /** Verify stuff that is common to both subtitles and closed captions */ void verify_smpte_timed_text_asset ( + Context& context, shared_ptr<const SMPTESubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - vector<VerificationNote>& notes + optional<int64_t> reel_asset_duration ) { if (asset->language()) { - verify_language_tag (*asset->language(), notes); + verify_language_tag(context, *asset->language()); } else { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file() }); + context.bv21_error(VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file()); } auto const size = 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<string>(size), *asset->file() } - ); + context.bv21_error(VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, raw_convert<string>(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" @@ -730,54 +781,48 @@ verify_smpte_timed_text_asset ( 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<string>(total_size), asset->file().get() }); + context.bv21_error(VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, raw_convert<string>(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() }); + context.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() }); + context.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() - }); + context.bv21_error( + VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION, + String::compose("%1 %2", *reel_asset_duration, asset->intrinsic_duration()), + asset->file().get() + ); } } /** Verify Interop subtitle / CCAP stuff */ void -verify_interop_text_asset(shared_ptr<const InteropSubtitleAsset> asset, vector<VerificationNote>& notes) +verify_interop_text_asset(Context& context, shared_ptr<const InteropSubtitleAsset> asset) { if (asset->subtitles().empty()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_SUBTITLE, asset->id(), asset->file().get() }); + context.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() }); + context.error(VerificationNote::Code::MISSING_FONT, unresolved.front()); } } /** Verify SMPTE subtitle-only stuff */ void -verify_smpte_subtitle_asset ( - shared_ptr<const SMPTESubtitleAsset> asset, - vector<VerificationNote>& notes, - State& state - ) +verify_smpte_subtitle_asset(Context& context, shared_ptr<const SMPTESubtitleAsset> asset) { 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 }); + if (!context.subtitle_language) { + context.subtitle_language = *asset->language(); + } else if (context.subtitle_language != *asset->language()) { + context.bv21_error(VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES); } } @@ -785,14 +830,14 @@ verify_smpte_subtitle_asset ( auto xml_id = asset->xml_id(); if (xml_id) { if (asset->resource_id().get() != xml_id) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID }); + context.bv21_error(VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID); } if (asset->id() == asset->resource_id().get() || asset->id() == xml_id) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID }); + context.bv21_error(VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID); } } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } if (asset->raw_xml()) { @@ -802,7 +847,7 @@ verify_smpte_subtitle_asset ( auto issue_date = doc.string_child("IssueDate"); std::regex reg("^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"); if (!std::regex_match(issue_date, reg)) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, issue_date}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, issue_date); } } } @@ -810,23 +855,16 @@ verify_smpte_subtitle_asset ( /** Verify all subtitle stuff */ static void -verify_subtitle_asset ( - shared_ptr<const SubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes, - State& state - ) +verify_subtitle_asset(Context& context, shared_ptr<const SubtitleAsset> asset, optional<int64_t> reel_asset_duration) { - stage ("Checking subtitle XML", asset->file()); + context.stage("Checking subtitle 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. */ if (asset->raw_xml()) { - validate_xml (asset->raw_xml().get(), xsd_dtd_directory, notes); + validate_xml(context, asset->raw_xml().get()); } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } auto namespace_count = [](shared_ptr<const SubtitleAsset> asset, string root_node) { @@ -842,19 +880,19 @@ verify_subtitle_asset ( auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset); if (interop) { - verify_interop_text_asset(interop, notes); + verify_interop_text_asset(context, interop); if (namespace_count(asset, "DCSubtitle") > 1) { - notes.push_back({ VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }); + context.warning(VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()); } } auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset); if (smpte) { - verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); - verify_smpte_subtitle_asset (smpte, notes, state); + verify_smpte_timed_text_asset(context, smpte, reel_asset_duration); + verify_smpte_subtitle_asset(context, smpte); /* This asset may be encrypted and in that case we'll have no raw_xml() */ if (asset->raw_xml() && namespace_count(asset, "SubtitleReel") > 1) { - notes.push_back({ VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()}); + context.warning(VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()); } } } @@ -863,35 +901,33 @@ verify_subtitle_asset ( /** Verify all closed caption stuff */ static void verify_closed_caption_asset ( + Context& context, shared_ptr<const SubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes + optional<int64_t> reel_asset_duration ) { - stage ("Checking closed caption XML", asset->file()); + context.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. */ auto raw_xml = asset->raw_xml(); if (raw_xml) { - validate_xml (*raw_xml, xsd_dtd_directory, notes); + validate_xml(context, *raw_xml); if (raw_xml->size() > 256 * 1024) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(raw_xml->size()), *asset->file()}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(raw_xml->size()), *asset->file()); } } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset); if (interop) { - verify_interop_text_asset(interop, notes); + verify_interop_text_asset(context, interop); } auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset); if (smpte) { - verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); + verify_smpte_timed_text_asset(context, smpte, reel_asset_duration); } } @@ -900,10 +936,9 @@ verify_closed_caption_asset ( static void verify_text_details ( - dcp::Standard standard, + Context& context, vector<shared_ptr<Reel>> reels, int edit_rate, - vector<VerificationNote>& notes, std::function<bool (shared_ptr<Reel>)> check, std::function<optional<string> (shared_ptr<Reel>)> xml, std::function<int64_t (shared_ptr<Reel>)> duration, @@ -997,7 +1032,7 @@ verify_text_details ( auto reel_xml = xml(reels[i]); if (!reel_xml) { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); continue; } @@ -1008,7 +1043,7 @@ verify_text_details ( shared_ptr<cxml::Document> doc; optional<int> tcr; optional<Time> start_time; - switch (standard) { + switch (context.dcp->standard().get_value_or(dcp::Standard::SMPTE)) { case dcp::Standard::INTEROP: doc = make_shared<cxml::Document>("DCSubtitle"); doc->read_string (*reel_xml); @@ -1031,8 +1066,8 @@ verify_text_details ( } reel_offset = end; - if (standard == dcp::Standard::SMPTE && has_text && font_ids.empty()) { - notes.push_back(dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT).set_id(id(reels[i]))); + if (context.dcp->standard() && *context.dcp->standard() == dcp::Standard::SMPTE && has_text && font_ids.empty()) { + context.add_note(dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT).set_id(id(reels[i]))); } } @@ -1041,47 +1076,34 @@ verify_text_details ( } if (too_early) { - notes.push_back({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME); } if (too_short) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_DURATION - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_DURATION); } if (too_close) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_SPACING - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_SPACING); } if (reel_overlap) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY - }); + context.error(VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY); } if (empty_text) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::EMPTY_TEXT - }); + context.warning(VerificationNote::Code::EMPTY_TEXT); } if (missing_load_font_id) { - notes.push_back(dcp::VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id(*missing_load_font_id)); + context.add_note(dcp::VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id(*missing_load_font_id)); } } static void -verify_closed_caption_details ( - vector<shared_ptr<Reel>> reels, - vector<VerificationNote>& notes - ) +verify_closed_caption_details(Context& context, vector<shared_ptr<Reel>> reels) { std::function<void (cxml::ConstNodePtr node, std::vector<cxml::ConstNodePtr>& text_or_image)> find_text_or_image; find_text_or_image = [&find_text_or_image](cxml::ConstNodePtr node, std::vector<cxml::ConstNodePtr>& text_or_image) { @@ -1147,7 +1169,7 @@ verify_closed_caption_details ( for (auto ccap: reel->closed_captions()) { auto reel_xml = ccap->asset()->raw_xml(); if (!reel_xml) { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); continue; } @@ -1170,15 +1192,11 @@ verify_closed_caption_details ( } if (mismatched_valign) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN, - }); + context.error(VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN); } if (incorrect_order) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING, - }); + context.error(VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING); } } @@ -1284,14 +1302,14 @@ dcp::verify_text_lines_and_characters( static void -verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vector<VerificationNote>& notes) +verify_text_details(Context& context, vector<shared_ptr<Reel>> reels) { if (reels.empty()) { return; } if (reels[0]->main_subtitle() && reels[0]->main_subtitle()->asset_ref().resolved()) { - verify_text_details(standard, reels, reels[0]->main_subtitle()->edit_rate().numerator, notes, + verify_text_details(context, reels, reels[0]->main_subtitle()->edit_rate().numerator, [](shared_ptr<Reel> reel) { return static_cast<bool>(reel->main_subtitle()); }, @@ -1308,7 +1326,7 @@ verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vect } for (auto i = 0U; i < reels[0]->closed_captions().size(); ++i) { - verify_text_details(standard, reels, reels[0]->closed_captions()[i]->edit_rate().numerator, notes, + verify_text_details(context, reels, reels[0]->closed_captions()[i]->edit_rate().numerator, [i](shared_ptr<Reel> reel) { return i < reel->closed_captions().size(); }, @@ -1324,12 +1342,12 @@ verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vect ); } - verify_closed_caption_details (reels, notes); + verify_closed_caption_details(context, reels); } void -verify_extension_metadata(shared_ptr<const CPL> cpl, vector<VerificationNote>& notes) +verify_extension_metadata(Context& context, shared_ptr<const CPL> cpl) { DCP_ASSERT (cpl->file()); cxml::Document doc ("CompositionPlaylist"); @@ -1379,9 +1397,9 @@ verify_extension_metadata(shared_ptr<const CPL> cpl, vector<VerificationNote>& n } if (missing) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()); } else if (!malformed.empty()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_EXTENSION_METADATA, malformed, cpl->file().get()}); + context.bv21_error(VerificationNote::Code::INVALID_EXTENSION_METADATA, malformed, cpl->file().get()); } } @@ -1414,17 +1432,10 @@ pkl_has_encrypted_assets(shared_ptr<const DCP> dcp, shared_ptr<const PKL> pkl) static void verify_reel( - shared_ptr<const DCP> dcp, - shared_ptr<const CPL> cpl, + Context& context, shared_ptr<const Reel> reel, int64_t start_frame, optional<dcp::Size> main_picture_active_area, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state, bool* have_main_subtitle, bool* have_no_main_subtitle, size_t* most_closed_captions, @@ -1434,24 +1445,24 @@ verify_reel( { for (auto i: reel->assets()) { if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_DURATION, i->id()}); + context.error(VerificationNote::Code::INVALID_DURATION, i->id()); } if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_INTRINSIC_DURATION, i->id()}); + context.error(VerificationNote::Code::INVALID_INTRINSIC_DURATION, i->id()); } auto file_asset = dynamic_pointer_cast<ReelFileAsset>(i); if (i->encryptable() && !file_asset->hash()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_HASH, i->id()}); + context.bv21_error(VerificationNote::Code::MISSING_HASH, i->id()); } } - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { boost::optional<int64_t> duration; for (auto i: reel->assets()) { if (!duration) { duration = i->actual_duration(); } else if (*duration != i->actual_duration()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_ASSET_DURATION}); + context.bv21_error(VerificationNote::Code::MISMATCHED_ASSET_DURATION); break; } } @@ -1468,32 +1479,26 @@ verify_reel( frame_rate.numerator != 50 && frame_rate.numerator != 60 && frame_rate.numerator != 96)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, - String::compose("%1/%2", frame_rate.numerator, frame_rate.denominator) - }); + context.error(VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, String::compose("%1/%2", frame_rate.numerator, frame_rate.denominator)); } /* Check asset */ if (reel->main_picture()->asset_ref().resolved()) { - verify_main_picture_asset(dcp, reel->main_picture(), start_frame, stage, progress, options, notes); + verify_main_picture_asset(context, reel->main_picture(), start_frame); auto const asset_size = reel->main_picture()->asset()->size(); if (main_picture_active_area) { if (main_picture_active_area->width > asset_size.width) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("width %1 is bigger than the asset width %2", main_picture_active_area->width, asset_size.width), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("width %1 is bigger than the asset width %2", main_picture_active_area->width, asset_size.width), + context.cpl->file().get() + ); } if (main_picture_active_area->height > asset_size.height) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("height %1 is bigger than the asset height %2", main_picture_active_area->height, asset_size.height), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("height %1 is bigger than the asset height %2", main_picture_active_area->height, asset_size.height), + context.cpl->file().get() + ); } } } @@ -1501,13 +1506,13 @@ verify_reel( } if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) { - verify_main_sound_asset(dcp, reel->main_sound(), stage, progress, options, notes, state); + verify_main_sound_asset(context, reel->main_sound()); } if (reel->main_subtitle()) { - verify_main_subtitle_reel(reel->main_subtitle(), notes); + verify_main_subtitle_reel(context, reel->main_subtitle()); if (reel->main_subtitle()->asset_ref().resolved()) { - verify_subtitle_asset(reel->main_subtitle()->asset(), reel->main_subtitle()->duration(), stage, xsd_dtd_directory, notes, state); + verify_subtitle_asset(context, reel->main_subtitle()->asset(), reel->main_subtitle()->duration()); } *have_main_subtitle = true; } else { @@ -1515,9 +1520,9 @@ verify_reel( } for (auto i: reel->closed_captions()) { - verify_closed_caption_reel(i, notes); + verify_closed_caption_reel(context, i); if (i->asset_ref().resolved()) { - verify_closed_caption_asset(i->asset(), i->duration(), stage, xsd_dtd_directory, notes); + verify_closed_caption_asset(context, i->asset(), i->duration()); } } @@ -1526,10 +1531,10 @@ verify_reel( markers_seen->insert(i); } if (reel->main_markers()->entry_point()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::UNEXPECTED_ENTRY_POINT}); + context.error(VerificationNote::Code::UNEXPECTED_ENTRY_POINT); } if (reel->main_markers()->duration()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::UNEXPECTED_DURATION}); + context.error(VerificationNote::Code::UNEXPECTED_DURATION); } } @@ -1541,26 +1546,21 @@ verify_reel( static void -verify_cpl( - shared_ptr<const DCP> dcp, - shared_ptr<const CPL> cpl, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state - ) +verify_cpl(Context& context, shared_ptr<const CPL> cpl) { - stage("Checking CPL", cpl->file()); - validate_xml(cpl->file().get(), xsd_dtd_directory, notes); + context.stage("Checking CPL", cpl->file()); + validate_xml(context, cpl->file().get()); if (cpl->any_encrypted() && !cpl->all_encrypted()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::PARTIALLY_ENCRYPTED}); + context.bv21_error(VerificationNote::Code::PARTIALLY_ENCRYPTED); + } else if (cpl->all_encrypted()) { + context.ok(VerificationNote::Code::ALL_ENCRYPTED); + } else if (!cpl->all_encrypted()) { + context.ok(VerificationNote::Code::NONE_ENCRYPTED); } for (auto const& i: cpl->additional_subtitle_languages()) { - verify_language_tag(i, notes); + verify_language_tag(context, i); } if (!cpl->content_kind().scope() || *cpl->content_kind().scope() == "http://www.smpte-ra.org/schemas/429-7/2006/CPL#standard-content") { @@ -1572,61 +1572,71 @@ verify_cpl( transform(name.begin(), name.end(), name.begin(), ::tolower); auto iter = std::find_if(all.begin(), all.end(), [name](ContentKind const& k) { return !k.scope() && k.name() == name; }); if (iter == all.end()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_CONTENT_KIND, cpl->content_kind().name()}); + context.error(VerificationNote::Code::INVALID_CONTENT_KIND, cpl->content_kind().name()); + } else { + context.ok(VerificationNote::Code::VALID_CONTENT_KIND, cpl->content_kind().name()); } } if (cpl->release_territory()) { if (!cpl->release_territory_scope() || cpl->release_territory_scope().get() != "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata#scope/release-territory/UNM49") { auto terr = cpl->release_territory().get(); + bool valid = true; /* Must be a valid region tag, or "001" */ try { LanguageTag::RegionSubtag test(terr); } catch (...) { if (terr != "001") { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_LANGUAGE, terr}); + context.bv21_error(VerificationNote::Code::INVALID_LANGUAGE, terr); + valid = false; } } + if (valid) { + context.ok(VerificationNote::Code::VALID_RELEASE_TERRITORY, terr); + } } } for (auto version: cpl->content_versions()) { if (version.label_text.empty()) { - notes.push_back( - dcp::VerificationNote(VerificationNote::Type::WARNING, VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id()) - ); + context.warning(VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()); break; + } else { + context.ok(VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, version.label_text); } } - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { if (!cpl->annotation_text()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->file().get()); } else if (cpl->annotation_text().get() != cpl->content_title_text()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), cpl->file().get()}); + context.warning(VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->file().get()); + } else { + context.ok(VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, cpl->annotation_text().get()); } } - for (auto i: dcp->pkls()) { + for (auto i: context.dcp->pkls()) { /* Check that the CPL's hash corresponds to the PKL */ optional<string> h = i->hash(cpl->id()); auto calculated_cpl_hash = make_digest(ArrayData(*cpl->file())); if (h && calculated_cpl_hash != *h) { - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_CPL_HASHES, - cpl->id(), cpl->file().get() ).set_calculated_hash(calculated_cpl_hash).set_reference_hash(*h) ); + } else { + context.ok(VerificationNote::Code::MATCHING_CPL_HASHES); } /* Check that any PKL with a single CPL has its AnnotationText the same as the CPL's ContentTitleText */ optional<string> required_annotation_text; for (auto j: i->assets()) { /* See if this is a CPL */ - for (auto k: dcp->cpls()) { + for (auto k: context.dcp->cpls()) { if (j->id() == k->id()) { if (!required_annotation_text) { /* First CPL we have found; this is the required AnnotationText unless we find another */ @@ -1640,7 +1650,9 @@ verify_cpl( } if (required_annotation_text && i->annotation_text() != required_annotation_text) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, i->id(), i->file().get()}); + context.bv21_error(VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, i->id(), i->file().get()); + } else { + context.ok(VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL); } } @@ -1655,38 +1667,39 @@ verify_cpl( map<Marker, Time> markers_seen; auto const main_picture_active_area = cpl->main_picture_active_area(); + bool active_area_ok = true; if (main_picture_active_area && (main_picture_active_area->width % 2)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("width %1 is not a multiple of 2", main_picture_active_area->width), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("width %1 is not a multiple of 2", main_picture_active_area->width), + cpl->file().get() + ); + active_area_ok = false; } if (main_picture_active_area && (main_picture_active_area->height % 2)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("height %1 is not a multiple of 2", main_picture_active_area->height), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("height %1 is not a multiple of 2", main_picture_active_area->height), + cpl->file().get() + ); + active_area_ok = false; + } + + if (main_picture_active_area && active_area_ok) { + context.ok( + VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, String::compose("%1x%2", main_picture_active_area->width, main_picture_active_area->height), + cpl->file().get() + ); } int64_t frame = 0; for (auto reel: cpl->reels()) { - stage("Checking reel", optional<boost::filesystem::path>()); + context.stage("Checking reel", optional<boost::filesystem::path>()); verify_reel( - dcp, - cpl, + context, reel, frame, main_picture_active_area, - stage, - xsd_dtd_directory, - progress, - options, - notes, - state, &have_main_subtitle, &have_no_main_subtitle, &most_closed_captions, @@ -1696,51 +1709,50 @@ verify_cpl( frame += reel->duration(); } - verify_text_details(dcp->standard().get_value_or(dcp::Standard::SMPTE), cpl->reels(), notes); + verify_text_details(context, cpl->reels()); - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { if (auto msc = cpl->main_sound_configuration()) { - if (state.audio_channels && msc->channels() != *state.audio_channels) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, - String::compose("MainSoundConfiguration has %1 channels but sound assets have %2", msc->channels(), *state.audio_channels), - cpl->file().get() - }); + if (context.audio_channels && msc->channels() != *context.audio_channels) { + context.error( + VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, + String::compose("MainSoundConfiguration has %1 channels but sound assets have %2", msc->channels(), *context.audio_channels), + cpl->file().get() + ); } } if (have_main_subtitle && have_no_main_subtitle) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS}); + context.bv21_error(VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS); } if (fewest_closed_captions != most_closed_captions) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS}); + context.bv21_error(VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS); } if (cpl->content_kind() == ContentKind::FEATURE) { if (markers_seen.find(Marker::FFEC) == markers_seen.end()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_FFEC_IN_FEATURE}); + context.bv21_error(VerificationNote::Code::MISSING_FFEC_IN_FEATURE); } if (markers_seen.find(Marker::FFMC) == markers_seen.end()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_FFMC_IN_FEATURE}); + context.bv21_error(VerificationNote::Code::MISSING_FFMC_IN_FEATURE); } } auto ffoc = markers_seen.find(Marker::FFOC); if (ffoc == markers_seen.end()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISSING_FFOC}); + context.warning(VerificationNote::Code::MISSING_FFOC); } else if (ffoc->second.e != 1) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_FFOC, raw_convert<string>(ffoc->second.e)}); + context.warning(VerificationNote::Code::INCORRECT_FFOC, raw_convert<string>(ffoc->second.e)); } auto lfoc = markers_seen.find(Marker::LFOC); if (lfoc == markers_seen.end()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISSING_LFOC}); + context.warning(VerificationNote::Code::MISSING_LFOC); } else { auto lfoc_time = lfoc->second.as_editable_units_ceil(lfoc->second.tcr); if (lfoc_time != (cpl->reels().back()->duration() - 1)) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_LFOC, raw_convert<string>(lfoc_time)}); + context.warning(VerificationNote::Code::INCORRECT_LFOC, raw_convert<string>(lfoc_time)); } } @@ -1752,12 +1764,12 @@ verify_cpl( } if (result.line_count_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT); } if (result.error_length_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH); } else if (result.warning_length_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH}); + context.warning(VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH); } result = LinesCharactersResult(); @@ -1770,26 +1782,26 @@ verify_cpl( } if (result.line_count_exceeded) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT); } if (result.error_length_exceeded) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH); } if (!cpl->read_composition_metadata()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()); } else if (!cpl->version_number()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get()); } - verify_extension_metadata(cpl, notes); + verify_extension_metadata(context, cpl); if (cpl->any_encrypted()) { cxml::Document doc("CompositionPlaylist"); DCP_ASSERT(cpl->file()); doc.read_file(dcp::filesystem::fix_long_path(cpl->file().get())); if (!doc.optional_node_child("Signature")) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->file().get()); } } } @@ -1798,27 +1810,22 @@ verify_cpl( static void -verify_pkl( - shared_ptr<const DCP> dcp, - shared_ptr<const PKL> pkl, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes - ) +verify_pkl(Context& context, shared_ptr<const PKL> pkl) { - validate_xml(pkl->file().get(), xsd_dtd_directory, notes); + validate_xml(context, pkl->file().get()); - if (pkl_has_encrypted_assets(dcp, pkl)) { + if (pkl_has_encrypted_assets(context.dcp, pkl)) { cxml::Document doc("PackingList"); doc.read_file(dcp::filesystem::fix_long_path(pkl->file().get())); if (!doc.optional_node_child("Signature")) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl->id(), pkl->file().get()}); + context.bv21_error(VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl->id(), pkl->file().get()); } } set<string> uuid_set; for (auto asset: pkl->assets()) { if (!uuid_set.insert(asset->id()).second) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl->id(), pkl->file().get()}); + context.error(VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl->id(), pkl->file().get()); break; } } @@ -1828,28 +1835,24 @@ verify_pkl( static void -verify_assetmap( - shared_ptr<const DCP> dcp, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes - ) +verify_assetmap(Context& context, shared_ptr<const DCP> dcp) { auto asset_map = dcp->asset_map(); DCP_ASSERT(asset_map); - validate_xml(asset_map->file().get(), xsd_dtd_directory, notes); + validate_xml(context, asset_map->file().get()); set<string> uuid_set; for (auto const& asset: asset_map->assets()) { if (!uuid_set.insert(asset.id()).second) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map->id(), asset_map->file().get()}); + context.error(VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map->id(), asset_map->file().get()); break; } } } -vector<VerificationNote> +dcp::VerificationResult dcp::verify ( vector<boost::filesystem::path> directories, vector<dcp::DecryptedKDM> kdms, @@ -1865,7 +1868,7 @@ dcp::verify ( *xsd_dtd_directory = filesystem::canonical(*xsd_dtd_directory); vector<VerificationNote> notes; - State state{}; + Context context(notes, *xsd_dtd_directory, stage, progress, options); vector<shared_ptr<DCP>> dcps; for (auto i: directories) { @@ -1874,22 +1877,25 @@ dcp::verify ( for (auto dcp: dcps) { stage ("Checking DCP", dcp->directory()); + + context.dcp = dcp; + bool carry_on = true; try { dcp->read (¬es, true); } catch (MissingAssetmapError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); carry_on = false; } catch (ReadError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (XMLError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (MXFFileError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (BadURNUUIDError& e) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (cxml::Error& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } if (!carry_on) { @@ -1906,16 +1912,9 @@ dcp::verify ( for (auto cpl: dcp->cpls()) { try { - verify_cpl( - dcp, - cpl, - stage, - *xsd_dtd_directory, - progress, - options, - notes, - state - ); + context.cpl = cpl; + verify_cpl(context, cpl); + context.cpl.reset(); } catch (ReadError& e) { notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); } @@ -1923,23 +1922,23 @@ dcp::verify ( for (auto pkl: dcp->pkls()) { stage("Checking PKL", pkl->file()); - verify_pkl(dcp, pkl, *xsd_dtd_directory, notes); + verify_pkl(context, pkl); } if (dcp->asset_map_file()) { stage("Checking ASSETMAP", dcp->asset_map_file().get()); - verify_assetmap(dcp, *xsd_dtd_directory, notes); + verify_assetmap(context, dcp); } else { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSETMAP}); + context.error(VerificationNote::Code::MISSING_ASSETMAP); } } - return notes; + return { notes, dcps }; } string -dcp::note_to_string (VerificationNote note) +dcp::note_to_string(VerificationNote note, function<string (string)> process_string, function<string (string)> process_filename) { /** These strings should say what is wrong, incorporating any extra details (ID, filenames etc.). * @@ -1951,238 +1950,269 @@ dcp::note_to_string (VerificationNote note) * End messages with a full stop. * Messages should not mention whether or not their errors are a part of Bv2.1. */ + + auto filename = [note, process_filename]() { + return process_filename(note.file()->filename().string()); + }; + +#define compose(format, ...) String::compose(process_string(format), __VA_ARGS__) + switch (note.code()) { case VerificationNote::Code::FAILED_READ: - return *note.note(); + return process_string(*note.note()); + case VerificationNote::Code::MATCHING_CPL_HASHES: + return process_string("The hash of the CPL in the PKL matches the CPL file."); case VerificationNote::Code::MISMATCHED_CPL_HASHES: - return String::compose("The hash (%1) of the CPL (%2) in the PKL does not agree with the CPL file (%3).", note.reference_hash().get(), note.note().get(), note.calculated_hash().get()); + return compose("The hash (%1) of the CPL (%2) in the PKL does not agree with the CPL file (%3).", note.reference_hash().get(), note.cpl_id().get(), note.calculated_hash().get()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE: - return String::compose("The picture in a reel has an invalid frame rate %1.", note.note().get()); + return compose("The picture in a reel has an invalid frame rate %1.", note.note().get()); case VerificationNote::Code::INCORRECT_PICTURE_HASH: - return String::compose("The hash (%1) of the picture asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), note.file()->filename(), note.reference_hash().get()); + return compose("The hash (%1) of the picture asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), filename(), note.reference_hash().get()); + case VerificationNote::Code::CORRECT_PICTURE_HASH: + return compose("The picture asset %1 has the expected hashes in the CPL and PKL.", filename()); case VerificationNote::Code::MISMATCHED_PICTURE_HASHES: - return String::compose("The PKL and CPL hashes differ for the picture asset %1.", note.file()->filename()); + return compose("The PKL and CPL hashes differ for the picture asset %1.", filename()); case VerificationNote::Code::INCORRECT_SOUND_HASH: - return String::compose("The hash (%1) of the sound asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), note.file()->filename(), note.reference_hash().get()); + return compose("The hash (%1) of the sound asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), filename(), note.reference_hash().get()); case VerificationNote::Code::MISMATCHED_SOUND_HASHES: - return String::compose("The PKL and CPL hashes differ for the sound asset %1.", note.file()->filename()); + return compose("The PKL and CPL hashes differ for the sound asset %1.", filename()); case VerificationNote::Code::EMPTY_ASSET_PATH: - return "The asset map contains an empty asset path."; + return process_string("The asset map contains an empty asset path."); case VerificationNote::Code::MISSING_ASSET: - return String::compose("The file %1 for an asset in the asset map cannot be found.", note.file()->filename()); + return compose("The file %1 for an asset in the asset map cannot be found.", filename()); case VerificationNote::Code::MISMATCHED_STANDARD: - return "The DCP contains both SMPTE and Interop parts."; + return process_string("The DCP contains both SMPTE and Interop parts."); case VerificationNote::Code::INVALID_XML: - return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get()); + return compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), filename(), note.line().get()); case VerificationNote::Code::MISSING_ASSETMAP: - return "No valid ASSETMAP or ASSETMAP.xml was found."; + return process_string("No valid ASSETMAP or ASSETMAP.xml was found."); case VerificationNote::Code::INVALID_INTRINSIC_DURATION: - return String::compose("The intrinsic duration of the asset %1 is less than 1 second.", note.note().get()); + return compose("The intrinsic duration of the asset %1 is less than 1 second.", note.note().get()); case VerificationNote::Code::INVALID_DURATION: - return String::compose("The duration of the asset %1 is less than 1 second.", note.note().get()); + return compose("The duration of the asset %1 is less than 1 second.", note.note().get()); + case VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES: + return compose("Each frame of the picture asset %1 has a bit rate safely under the limit of 250Mbit/s.", filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES: - return String::compose( + return compose( "Frame %1 (timecode %2) in asset %3 has an instantaneous bit rate that is larger than the limit of 250Mbit/s.", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), - note.file()->filename() + filename() ); case VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES: - return String::compose( + return compose( "Frame %1 (timecode %2) in asset %3 has an instantaneous bit rate that is close to the limit of 250Mbit/s.", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), - note.file()->filename() + filename() ); case VerificationNote::Code::EXTERNAL_ASSET: - return String::compose("The asset %1 that this DCP refers to is not included in the DCP. It may be a VF.", note.note().get()); + return compose("The asset %1 that this DCP refers to is not included in the DCP. It may be a VF.", note.note().get()); case VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD: - return String::compose("The asset %1 is 3D but its MXF is marked as 2D.", note.file()->filename()); + return compose("The asset %1 is 3D but its MXF is marked as 2D.", filename()); case VerificationNote::Code::INVALID_STANDARD: return "This DCP does not use the SMPTE standard."; case VerificationNote::Code::INVALID_LANGUAGE: - return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get()); + return compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get()); + case VerificationNote::Code::VALID_RELEASE_TERRITORY: + return compose("Valid release territory %1.", note.note().get()); case VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS: - return String::compose("The size %1 of picture asset %2 is not allowed.", note.note().get(), note.file()->filename()); + return compose("The size %1 of picture asset %2 is not allowed.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K: - return String::compose("The frame rate %1 of picture asset %2 is not allowed for 2K DCPs.", note.note().get(), note.file()->filename()); + return compose("The frame rate %1 of picture asset %2 is not allowed for 2K DCPs.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K: - return String::compose("The frame rate %1 of picture asset %2 is not allowed for 4K DCPs.", note.note().get(), note.file()->filename()); + return compose("The frame rate %1 of picture asset %2 is not allowed for 4K DCPs.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D: - return "3D 4K DCPs are not allowed."; + return process_string("3D 4K DCPs are not allowed."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES: - return String::compose("The size %1 of the closed caption asset %2 is larger than the 256KB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the closed caption asset %2 is larger than the 256KB maximum.", note.note().get(), filename()); case VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES: - return String::compose("The size %1 of the timed text asset %2 is larger than the 115MB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the timed text asset %2 is larger than the 115MB maximum.", note.note().get(), filename()); case VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES: - return String::compose("The size %1 of the fonts in timed text asset %2 is larger than the 10MB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the fonts in timed text asset %2 is larger than the 10MB maximum.", note.note().get(), filename()); case VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE: - return String::compose("The XML for the SMPTE subtitle asset %1 has no <Language> tag.", note.file()->filename()); + return compose("The XML for the SMPTE subtitle asset %1 has no <Language> tag.", filename()); case VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES: - return "Some subtitle assets have different <Language> tags than others"; + return process_string("Some subtitle assets have different <Language> tags than others"); case VerificationNote::Code::MISSING_SUBTITLE_START_TIME: - return String::compose("The XML for the SMPTE subtitle asset %1 has no <StartTime> tag.", note.file()->filename()); + return compose("The XML for the SMPTE subtitle asset %1 has no <StartTime> tag.", filename()); case VerificationNote::Code::INVALID_SUBTITLE_START_TIME: - return String::compose("The XML for a SMPTE subtitle asset %1 has a non-zero <StartTime> tag.", note.file()->filename()); + return compose("The XML for a SMPTE subtitle asset %1 has a non-zero <StartTime> tag.", filename()); case VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME: - return "The first subtitle or closed caption is less than 4 seconds from the start of the DCP."; + return process_string("The first subtitle or closed caption is less than 4 seconds from the start of the DCP."); case VerificationNote::Code::INVALID_SUBTITLE_DURATION: - return "At least one subtitle lasts less than 15 frames."; + return process_string("At least one subtitle lasts less than 15 frames."); case VerificationNote::Code::INVALID_SUBTITLE_SPACING: - return "At least one pair of subtitles is separated by less than 2 frames."; + return process_string("At least one pair of subtitles is separated by less than 2 frames."); case VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY: - return "At least one subtitle extends outside of its reel."; + return process_string("At least one subtitle extends outside of its reel."); case VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT: - return "There are more than 3 subtitle lines in at least one place in the DCP."; + return process_string("There are more than 3 subtitle lines in at least one place in the DCP."); case VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH: - return "There are more than 52 characters in at least one subtitle line."; + return process_string("There are more than 52 characters in at least one subtitle line."); case VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH: - return "There are more than 79 characters in at least one subtitle line."; + return process_string("There are more than 79 characters in at least one subtitle line."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT: - return "There are more than 3 closed caption lines in at least one place."; + return process_string("There are more than 3 closed caption lines in at least one place."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH: - return "There are more than 32 characters in at least one closed caption line."; + return process_string("There are more than 32 characters in at least one closed caption line."); case VerificationNote::Code::INVALID_SOUND_FRAME_RATE: - return String::compose("The sound asset %1 has a sampling rate of %2", note.file()->filename(), note.note().get()); + return compose("The sound asset %1 has a sampling rate of %2", filename(), note.note().get()); + case VerificationNote::Code::INVALID_SOUND_BIT_DEPTH: + return compose("The sound asset %1 has a bit depth of %2", filename(), note.note().get()); case VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT: - return String::compose("The CPL %1 has no <AnnotationText> tag.", note.note().get()); + return compose("The CPL %1 has no <AnnotationText> tag.", note.cpl_id().get()); case VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT: - return String::compose("The CPL %1 has an <AnnotationText> which differs from its <ContentTitleText>.", note.note().get()); + return compose("The CPL %1 has an <AnnotationText> which differs from its <ContentTitleText>.", note.cpl_id().get()); + case VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT: + return compose("Valid CPL annotation text %1", note.note().get()); case VerificationNote::Code::MISMATCHED_ASSET_DURATION: - return "All assets in a reel do not have the same duration."; + return process_string("All assets in a reel do not have the same duration."); case VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS: - return "At least one reel contains a subtitle asset, but some reel(s) do not."; + return process_string("At least one reel contains a subtitle asset, but some reel(s) do not."); case VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS: - return "At least one reel has closed captions, but reels have different numbers of closed caption assets."; + return process_string("At least one reel has closed captions, but reels have different numbers of closed caption assets."); case VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT: - return String::compose("The subtitle asset %1 has no <EntryPoint> tag.", note.note().get()); + return compose("The subtitle asset %1 has no <EntryPoint> tag.", note.note().get()); case VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT: - return String::compose("The subtitle asset %1 has an <EntryPoint> other than 0.", note.note().get()); + return compose("The subtitle asset %1 has an <EntryPoint> other than 0.", note.note().get()); case VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT: - return String::compose("The closed caption asset %1 has no <EntryPoint> tag.", note.note().get()); + return compose("The closed caption asset %1 has no <EntryPoint> tag.", note.note().get()); case VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT: - return String::compose("The closed caption asset %1 has an <EntryPoint> other than 0.", note.note().get()); + return compose("The closed caption asset %1 has an <EntryPoint> other than 0.", note.note().get()); case VerificationNote::Code::MISSING_HASH: - return String::compose("The asset %1 has no <Hash> tag in the CPL.", note.note().get()); + return compose("The asset %1 has no <Hash> tag in the CPL.", note.note().get()); case VerificationNote::Code::MISSING_FFEC_IN_FEATURE: - return "The DCP is marked as a Feature but there is no FFEC (first frame of end credits) marker."; + return process_string("The DCP is marked as a Feature but there is no FFEC (first frame of end credits) marker."); case VerificationNote::Code::MISSING_FFMC_IN_FEATURE: - return "The DCP is marked as a Feature but there is no FFMC (first frame of moving credits) marker."; + return process_string("The DCP is marked as a Feature but there is no FFMC (first frame of moving credits) marker."); case VerificationNote::Code::MISSING_FFOC: - return "There should be a FFOC (first frame of content) marker."; + return process_string("There should be a FFOC (first frame of content) marker."); case VerificationNote::Code::MISSING_LFOC: - return "There should be a LFOC (last frame of content) marker."; + return process_string("There should be a LFOC (last frame of content) marker."); case VerificationNote::Code::INCORRECT_FFOC: - return String::compose("The FFOC marker is %1 instead of 1", note.note().get()); + return compose("The FFOC marker is %1 instead of 1", note.note().get()); case VerificationNote::Code::INCORRECT_LFOC: - return String::compose("The LFOC marker is %1 instead of 1 less than the duration of the last reel.", note.note().get()); + return compose("The LFOC marker is %1 instead of 1 less than the duration of the last reel.", note.note().get()); case VerificationNote::Code::MISSING_CPL_METADATA: - return String::compose("The CPL %1 has no <CompositionMetadataAsset> tag.", note.note().get()); + return compose("The CPL %1 has no <CompositionMetadataAsset> tag.", note.cpl_id().get()); case VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER: - return String::compose("The CPL %1 has no <VersionNumber> in its <CompositionMetadataAsset>.", note.note().get()); + return compose("The CPL %1 has no <VersionNumber> in its <CompositionMetadataAsset>.", note.cpl_id().get()); case VerificationNote::Code::MISSING_EXTENSION_METADATA: - return String::compose("The CPL %1 has no <ExtensionMetadata> in its <CompositionMetadataAsset>.", note.note().get()); + return compose("The CPL %1 has no <ExtensionMetadata> in its <CompositionMetadataAsset>.", note.cpl_id().get()); case VerificationNote::Code::INVALID_EXTENSION_METADATA: - return String::compose("The CPL %1 has a malformed <ExtensionMetadata> (%2).", note.file()->filename(), note.note().get()); + return compose("The CPL %1 has a malformed <ExtensionMetadata> (%2).", filename(), note.note().get()); case VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT: - return String::compose("The CPL %1, which has encrypted content, is not signed.", note.note().get()); + return compose("The CPL %1, which has encrypted content, is not signed.", note.cpl_id().get()); case VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT: - return String::compose("The PKL %1, which has encrypted content, is not signed.", note.note().get()); + return compose("The PKL %1, which has encrypted content, is not signed.", note.note().get()); case VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL: - return String::compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>.", note.note().get()); + return compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>.", note.note().get()); + case VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL: + return process_string("The PKL and CPL annotation texts match."); + case VerificationNote::Code::ALL_ENCRYPTED: + return process_string("All the assets are encrypted."); + case VerificationNote::Code::NONE_ENCRYPTED: + return process_string("All the assets are unencrypted."); case VerificationNote::Code::PARTIALLY_ENCRYPTED: - return "Some assets are encrypted but some are not."; + return process_string("Some assets are encrypted but some are not."); case VerificationNote::Code::INVALID_JPEG2000_CODESTREAM: - return String::compose( + return compose( "Frame %1 (timecode %2) has an invalid JPEG2000 codestream (%3).", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), note.note().get() ); case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K: - return String::compose("The JPEG2000 codestream uses %1 guard bits in a 2K image instead of 1.", note.note().get()); + return compose("The JPEG2000 codestream uses %1 guard bits in a 2K image instead of 1.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K: - return String::compose("The JPEG2000 codestream uses %1 guard bits in a 4K image instead of 2.", note.note().get()); + return compose("The JPEG2000 codestream uses %1 guard bits in a 4K image instead of 2.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE: - return "The JPEG2000 tile size is not the same as the image size."; + return process_string("The JPEG2000 tile size is not the same as the image size."); case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH: - return String::compose("The JPEG2000 codestream uses a code block width of %1 instead of 32.", note.note().get()); + return compose("The JPEG2000 codestream uses a code block width of %1 instead of 32.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT: - return String::compose("The JPEG2000 codestream uses a code block height of %1 instead of 32.", note.note().get()); + return compose("The JPEG2000 codestream uses a code block height of %1 instead of 32.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K: - return String::compose("%1 POC markers found in 2K JPEG2000 codestream instead of 0.", note.note().get()); + return compose("%1 POC markers found in 2K JPEG2000 codestream instead of 0.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K: - return String::compose("%1 POC markers found in 4K JPEG2000 codestream instead of 1.", note.note().get()); + return compose("%1 POC markers found in 4K JPEG2000 codestream instead of 1.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER: - return String::compose("Incorrect POC marker content found (%1).", note.note().get()); + return compose("Incorrect POC marker content found (%1).", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION: - return "POC marker found outside main header."; + return process_string("POC marker found outside main header."); case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K: - return String::compose("The JPEG2000 codestream has %1 tile parts in a 2K image instead of 3.", note.note().get()); + return compose("The JPEG2000 codestream has %1 tile parts in a 2K image instead of 3.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K: - return String::compose("The JPEG2000 codestream has %1 tile parts in a 4K image instead of 6.", note.note().get()); + return compose("The JPEG2000 codestream has %1 tile parts in a 4K image instead of 6.", note.note().get()); case VerificationNote::Code::MISSING_JPEG200_TLM_MARKER: - return "No TLM marker was found in a JPEG2000 codestream."; + return process_string("No TLM marker was found in a JPEG2000 codestream."); case VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID: - return "The Resource ID in a timed text MXF did not match the ID of the contained XML."; + return process_string("The Resource ID in a timed text MXF did not match the ID of the contained XML."); case VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID: - return "The Asset ID in a timed text MXF is the same as the Resource ID or that of the contained XML."; + return process_string("The Asset ID in a timed text MXF is the same as the Resource ID or that of the contained XML."); case VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION: { vector<string> parts; boost::split (parts, note.note().get(), boost::is_any_of(" ")); DCP_ASSERT (parts.size() == 2); - return String::compose("The reel duration of some timed text (%1) is not the same as the ContainerDuration of its MXF (%2).", parts[0], parts[1]); + return compose("The reel duration of some timed text (%1) is not the same as the ContainerDuration of its MXF (%2).", parts[0], parts[1]); } case VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED: - return "Some aspect of this DCP could not be checked because it is encrypted."; + return process_string("Some aspect of this DCP could not be checked because it is encrypted."); case VerificationNote::Code::EMPTY_TEXT: - return "There is an empty <Text> node in a subtitle or closed caption."; + return process_string("There is an empty <Text> node in a subtitle or closed caption."); case VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN: - return "Some closed <Text> or <Image> nodes have different vertical alignments within a <Subtitle>."; + return process_string("Some closed <Text> or <Image> nodes have different vertical alignments within a <Subtitle>."); case VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING: - return "Some closed captions are not listed in the order of their vertical position."; + return process_string("Some closed captions are not listed in the order of their vertical position."); case VerificationNote::Code::UNEXPECTED_ENTRY_POINT: - return "There is an <EntryPoint> node inside a <MainMarkers>."; + return process_string("There is an <EntryPoint> node inside a <MainMarkers>."); case VerificationNote::Code::UNEXPECTED_DURATION: - return "There is an <Duration> node inside a <MainMarkers>."; + return process_string("There is an <Duration> node inside a <MainMarkers>."); case VerificationNote::Code::INVALID_CONTENT_KIND: - return String::compose("<ContentKind> has an invalid value %1.", note.note().get()); + return compose("<ContentKind> has an invalid value %1.", note.note().get()); + case VerificationNote::Code::VALID_CONTENT_KIND: + return compose("Valid <ContentKind> %1.", note.note().get()); case VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA: - return String::compose("<MainPictureActiveaArea> has an invalid value: %1", note.note().get()); + return compose("<MainPictureActiveaArea> has an invalid value: %1", note.note().get()); + case VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA: + return compose("<MainPictureActiveaArea> %1 is valid", note.note().get()); case VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL: - return String::compose("The PKL %1 has more than one asset with the same ID.", note.note().get()); + return compose("The PKL %1 has more than one asset with the same ID.", note.note().get()); case VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP: - return String::compose("The ASSETMAP %1 has more than one asset with the same ID.", note.note().get()); + return compose("The ASSETMAP %1 has more than one asset with the same ID.", note.note().get()); case VerificationNote::Code::MISSING_SUBTITLE: - return String::compose("The subtitle asset %1 has no subtitles.", note.note().get()); + return compose("The subtitle asset %1 has no subtitles.", note.note().get()); case VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE: - return String::compose("<IssueDate> has an invalid value: %1", note.note().get()); + return compose("<IssueDate> has an invalid value: %1", note.note().get()); case VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS: - return String::compose("The sound assets do not all have the same channel count; the first to differ is %1", note.file()->filename()); + return compose("The sound assets do not all have the same channel count; the first to differ is %1", filename()); case VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION: - return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get()); + return 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 compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE: - return String::compose( + return compose( "Frame %1 has an image component that is too large (component %2 is %3 bytes in size).", note.frame().get(), note.component().get(), note.size().get() ); case VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT: - return String::compose("The XML in the subtitle asset %1 has more than one namespace declaration.", note.note().get()); + return compose("The XML in the subtitle asset %1 has more than one namespace declaration.", note.note().get()); case VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT: - return String::compose("A subtitle or closed caption refers to a font with ID %1 that does not have a corresponding <LoadFont> node", note.id().get()); + return compose("A subtitle or closed caption refers to a font with ID %1 that does not have a corresponding <LoadFont> node", note.id().get()); case VerificationNote::Code::MISSING_LOAD_FONT: - return String::compose("The SMPTE subtitle asset %1 has <Text> nodes but no <LoadFont> node", note.id().get()); + return compose("The SMPTE subtitle asset %1 has <Text> nodes but no <LoadFont> node", note.id().get()); case VerificationNote::Code::MISMATCHED_ASSET_MAP_ID: - return String::compose("The asset with ID %1 in the asset map actually has an id of %2", note.id().get(), note.other_id().get()); + return compose("The asset with ID %1 in the asset map actually has an id of %2", note.id().get(), note.other_id().get()); case VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT: - return String::compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.id().get()); + return compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.cpl_id().get()); + case VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT: + return compose("CPL has valid <ContentVersion> %1", note.note().get()); case VerificationNote::Code::INVALID_CPL_NAMESPACE: - return String::compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.file()->filename()); + return compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.cpl_id().get()); case VerificationNote::Code::MISSING_CPL_CONTENT_VERSION: - return String::compose("The CPL %1 has no <ContentVersion> tag", note.note().get()); + return compose("The CPL %1 has no <ContentVersion> tag", note.cpl_id().get()); } return ""; @@ -2203,12 +2233,20 @@ dcp::operator== (dcp::VerificationNote const& a, dcp::VerificationNote const& b) a.id() == b.id() && a.other_id() == b.other_id() && a.frame_rate() == b.frame_rate() && + a.cpl_id() == b.cpl_id() && a.reference_hash() == b.reference_hash() && a.calculated_hash() == b.calculated_hash(); } bool +dcp::operator!=(dcp::VerificationNote const& a, dcp::VerificationNote const& b) +{ + return !(a == b); +} + + +bool dcp::operator< (dcp::VerificationNote const& a, dcp::VerificationNote const& b) { if (a.type() != b.type()) { |
