diff options
Diffstat (limited to 'src')
39 files changed, 1199 insertions, 727 deletions
diff --git a/src/asset_factory.cc b/src/asset_factory.cc index be4f6b49..9eb42c8b 100644 --- a/src/asset_factory.cc +++ b/src/asset_factory.cc @@ -61,7 +61,8 @@ dcp::asset_factory (boost::filesystem::path path, bool ignore_incorrect_picture_ */ ASDCP::EssenceType_t type; - auto const result = ASDCP::EssenceType(dcp::filesystem::fix_long_path(path).string().c_str(), type); + Kumu::FileReaderFactory factory; + auto const result = ASDCP::EssenceType(dcp::filesystem::fix_long_path(path).string().c_str(), type, factory); if (!ASDCP_SUCCESS(result)) { throw ReadError(String::compose("Could not find essence type (%1)", result.Message()), path.string()); } diff --git a/src/asset_map.cc b/src/asset_map.cc index 281f27c3..0ee4b486 100644 --- a/src/asset_map.cc +++ b/src/asset_map.cc @@ -165,29 +165,29 @@ AssetMap::write_xml(boost::filesystem::path file) const DCP_ASSERT (false); } - root->add_child("Id")->add_child_text("urn:uuid:" + _id); + cxml::add_text_child(root, "Id", "urn:uuid:" + _id); if (_annotation_text) { - root->add_child("AnnotationText")->add_child_text(*_annotation_text); + cxml::add_text_child(root, "AnnotationText", *_annotation_text); } switch (_standard) { case Standard::INTEROP: - root->add_child("VolumeCount")->add_child_text("1"); - root->add_child("IssueDate")->add_child_text(_issue_date); - root->add_child("Issuer")->add_child_text(_issuer); - root->add_child("Creator")->add_child_text(_creator); + cxml::add_text_child(root, "VolumeCount", "1"); + cxml::add_text_child(root, "IssueDate", _issue_date); + cxml::add_text_child(root, "Issuer", _issuer); + cxml::add_text_child(root, "Creator", _creator); break; case Standard::SMPTE: - root->add_child("Creator")->add_child_text(_creator); - root->add_child("VolumeCount")->add_child_text("1"); - root->add_child("IssueDate")->add_child_text(_issue_date); - root->add_child("Issuer")->add_child_text(_issuer); + cxml::add_text_child(root, "Creator", _creator); + cxml::add_text_child(root, "VolumeCount", "1"); + cxml::add_text_child(root, "IssueDate", _issue_date); + cxml::add_text_child(root, "Issuer", _issuer); break; default: DCP_ASSERT (false); } - auto asset_list = root->add_child("AssetList"); + auto asset_list = cxml::add_child(root, "AssetList"); for (auto const& asset: _assets) { asset.write_xml(asset_list, file.parent_path()); } @@ -200,20 +200,20 @@ AssetMap::write_xml(boost::filesystem::path file) const void AssetMap::Asset::write_xml(xmlpp::Element* asset_list, boost::filesystem::path dcp_root_directory) const { - auto node = asset_list->add_child("Asset"); - node->add_child("Id")->add_child_text("urn:uuid:" + _id); + auto node = cxml::add_child(asset_list, "Asset"); + cxml::add_text_child(node, "Id", "urn:uuid:" + _id); if (_pkl) { - node->add_child("PackingList")->add_child_text("true"); + cxml::add_text_child(node, "PackingList", "true"); } - auto chunk_list = node->add_child("ChunkList"); - auto chunk = chunk_list->add_child("Chunk"); + auto chunk_list = cxml::add_child(node, "ChunkList"); + auto chunk = cxml::add_child(chunk_list, "Chunk"); auto relative_path = relative_to_root(filesystem::canonical(dcp_root_directory), filesystem::canonical(_path)); DCP_ASSERT(relative_path); - chunk->add_child("Path")->add_child_text(relative_path->generic_string()); - chunk->add_child("VolumeIndex")->add_child_text("1"); - chunk->add_child("Offset")->add_child_text("0"); - chunk->add_child("Length")->add_child_text(raw_convert<string>(filesystem::file_size(_path))); + cxml::add_text_child(chunk, "Path", relative_path->generic_string()); + cxml::add_text_child(chunk, "VolumeIndex", "1"); + cxml::add_text_child(chunk, "Offset", "0"); + cxml::add_text_child(chunk, "Length", raw_convert<string>(filesystem::file_size(_path))); } diff --git a/src/asset_reader.h b/src/asset_reader.h index c8953e09..aa815745 100644 --- a/src/asset_reader.h +++ b/src/asset_reader.h @@ -97,7 +97,8 @@ private: explicit AssetReader (Asset const * asset, boost::optional<Key> key, Standard standard) : _crypto_context (new DecryptionContext(key, standard)) { - _reader = new R (); + Kumu::FileReaderFactory factory; + _reader = new R(factory); DCP_ASSERT (asset->file()); auto const r = _reader->OpenRead(dcp::filesystem::fix_long_path(*asset->file()).string().c_str()); if (ASDCP_FAILURE(r)) { diff --git a/src/atmos_asset.cc b/src/atmos_asset.cc index 42a0774e..09a22c1e 100644 --- a/src/atmos_asset.cc +++ b/src/atmos_asset.cc @@ -42,6 +42,7 @@ #include "atmos_asset_writer.h" #include "exceptions.h" #include <asdcp/AS_DCP.h> +#include <asdcp/KM_fileio.h> using std::string; @@ -67,7 +68,8 @@ AtmosAsset::AtmosAsset (boost::filesystem::path file) : Asset (file) , MXF (Standard::SMPTE) { - ASDCP::ATMOS::MXFReader reader; + Kumu::FileReaderFactory factory; + ASDCP::ATMOS::MXFReader reader(factory); auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); diff --git a/src/certificate_chain.cc b/src/certificate_chain.cc index c4e3a9b0..2bbddc7f 100644 --- a/src/certificate_chain.cc +++ b/src/certificate_chain.cc @@ -606,47 +606,47 @@ CertificateChain::sign (xmlpp::Element* parent, Standard standard) const /* <Signer> */ parent->add_child_text(" "); - auto signer = parent->add_child("Signer"); + auto signer = cxml::add_child(parent, "Signer"); signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); - auto data = signer->add_child("X509Data", "dsig"); - auto serial_element = data->add_child("X509IssuerSerial", "dsig"); - serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer()); - serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial()); - data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject()); + auto data = cxml::add_child(signer, "X509Data", string("dsig")); + auto serial_element = cxml::add_child(data, "X509IssuerSerial", string("dsig")); + cxml::add_child(serial_element, "X509IssuerName", string("dsig"))->add_child_text(leaf().issuer()); + cxml::add_child(serial_element, "X509SerialNumber", string("dsig"))->add_child_text(leaf().serial()); + cxml::add_child(data, "X509SubjectName", string("dsig"))->add_child_text(leaf().subject()); indent (signer, 2); /* <Signature> */ parent->add_child_text("\n "); - auto signature = parent->add_child("Signature"); + auto signature = cxml::add_child(parent, "Signature"); signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig"); signature->set_namespace ("dsig"); parent->add_child_text("\n"); - auto signed_info = signature->add_child ("SignedInfo", "dsig"); - signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); + auto signed_info = cxml::add_child(signature, "SignedInfo", string("dsig")); + cxml::add_child(signed_info, "CanonicalizationMethod", string("dsig"))->set_attribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); if (standard == Standard::INTEROP) { - signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"); + cxml::add_child(signed_info, "SignatureMethod", string("dsig"))->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"); } else { - signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); + cxml::add_child(signed_info, "SignatureMethod", string("dsig"))->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); } - auto reference = signed_info->add_child("Reference", "dsig"); + auto reference = cxml::add_child(signed_info, "Reference", string("dsig")); reference->set_attribute ("URI", ""); - auto transforms = reference->add_child("Transforms", "dsig"); - transforms->add_child("Transform", "dsig")->set_attribute ( + auto transforms = cxml::add_child(reference, "Transforms", string("dsig")); + cxml::add_child(transforms, "Transform", string("dsig"))->set_attribute( "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature" ); - reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); + cxml::add_child(reference, "DigestMethod", string("dsig"))->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); /* This will be filled in by the signing later */ - reference->add_child("DigestValue", "dsig"); + cxml::add_child(reference, "DigestValue", string("dsig")); - signature->add_child("SignatureValue", "dsig"); - signature->add_child("KeyInfo", "dsig"); + cxml::add_child(signature, "SignatureValue", string("dsig")); + cxml::add_child(signature, "KeyInfo", string("dsig")); add_signature_value (signature, "dsig", true); } @@ -655,19 +655,20 @@ void CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const { cxml::Node cp (parent); - auto key_info = cp.node_child("KeyInfo")->node(); + auto key_info = dynamic_cast<xmlpp::Element*>(cp.node_child("KeyInfo")->node()); + DCP_ASSERT(key_info); /* Add the certificate chain to the KeyInfo child node of parent */ for (auto const& i: leaf_to_root()) { - auto data = key_info->add_child("X509Data", ns); + auto data = cxml::add_child(key_info, "X509Data", ns); { - auto serial = data->add_child("X509IssuerSerial", ns); - serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ()); - serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ()); + auto serial = cxml::add_child(data, "X509IssuerSerial", ns); + cxml::add_child(serial, "X509IssuerName", ns)->add_child_text(i.issuer()); + cxml::add_child(serial, "X509SerialNumber", ns)->add_child_text(i.serial()); } - data->add_child("X509Certificate", ns)->add_child_text (i.certificate()); + cxml::add_child(data, "X509Certificate", ns)->add_child_text(i.certificate()); } auto signature_context = xmlSecDSigCtxCreate (0); @@ -213,15 +213,15 @@ CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns); } - root->add_child("Id")->add_child_text ("urn:uuid:" + _id); + cxml::add_text_child(root, "Id", "urn:uuid:" + _id); if (_annotation_text) { - root->add_child("AnnotationText")->add_child_text (*_annotation_text); + cxml::add_text_child(root, "AnnotationText", *_annotation_text); } - root->add_child("IssueDate")->add_child_text (_issue_date); - root->add_child("Issuer")->add_child_text (_issuer); - root->add_child("Creator")->add_child_text (_creator); - root->add_child("ContentTitleText")->add_child_text (_content_title_text); - auto content_kind = root->add_child("ContentKind"); + cxml::add_text_child(root, "IssueDate", _issue_date); + cxml::add_text_child(root, "Issuer", _issuer); + cxml::add_text_child(root, "Creator", _creator); + cxml::add_text_child(root, "ContentTitleText", _content_title_text); + auto content_kind = cxml::add_child(root, "ContentKind"); content_kind->add_child_text(_content_kind.name()); if (_content_kind.scope()) { content_kind->set_attribute("scope", *_content_kind.scope()); @@ -233,12 +233,12 @@ CPL::write_xml(boost::filesystem::path file, shared_ptr<const CertificateChain> _content_versions[0].as_xml (root); } - auto rating_list = root->add_child("RatingList"); + auto rating_list = cxml::add_child(root, "RatingList"); for (auto i: _ratings) { - i.as_xml (rating_list->add_child("Rating")); + i.as_xml(cxml::add_child(rating_list, "Rating")); } - auto reel_list = root->add_child ("ReelList"); + auto reel_list = cxml::add_child(root, "ReelList"); if (_reels.empty()) { throw NoReelsError (); @@ -403,27 +403,27 @@ CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsse reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield) ); if (KM_SUCCESS(r)) { - auto mca_subs = parent->add_child("mca:MCASubDescriptors"); + auto mca_subs = cxml::add_child(parent, "mca:MCASubDescriptors"); mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca"); mca_subs->set_namespace_declaration (smpte_395_ns, "r0"); mca_subs->set_namespace_declaration (smpte_335_ns, "r1"); - auto sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0"); + auto sf = cxml::add_child(mca_subs, "SoundfieldGroupLabelSubDescriptor", string("r0")); char buffer[64]; soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer)); - sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer)); + cxml::add_child(sf, "InstanceID", string("r1"))->add_child_text("urn:uuid:" + string(buffer)); soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer)); - sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer)); + cxml::add_child(sf, "MCALabelDictionaryID", string("r1"))->add_child_text("urn:smpte:ul:" + string(buffer)); soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer)); - sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer)); + cxml::add_child(sf, "MCALinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer)); soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer)); - sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer); + cxml::add_child(sf, "MCATagSymbol", string("r1"))->add_child_text(buffer); if (!soundfield->MCATagName.empty()) { soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer)); - sf->add_child("MCATagName", "r1")->add_child_text(buffer); + cxml::add_child(sf, "MCATagName", string("r1"))->add_child_text(buffer); } if (!soundfield->RFC5646SpokenLanguage.empty()) { soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer)); - sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer); + cxml::add_child(sf, "RFC5646SpokenLanguage", string("r1"))->add_child_text(buffer); } /* Find the MCA subdescriptors in the MXF so that we can also write them here */ @@ -435,29 +435,29 @@ CPL::write_mca_subdescriptors(xmlpp::Element* parent, shared_ptr<const SoundAsse for (auto i: channels) { auto channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i); - auto ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0"); + auto ch = cxml::add_child(mca_subs, "AudioChannelLabelSubDescriptor", string("r0")); channel->InstanceUID.EncodeString(buffer, sizeof(buffer)); - ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer)); + cxml::add_child(ch, "InstanceID", string("r1"))->add_child_text("urn:uuid:" + string(buffer)); channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer)); - ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer)); + cxml::add_child(ch, "MCALabelDictionaryID", string("r1"))->add_child_text("urn:smpte:ul:" + string(buffer)); channel->MCALinkID.EncodeString(buffer, sizeof(buffer)); - ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer)); + cxml::add_child(ch, "MCALinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer)); channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer)); - ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer); + cxml::add_child(ch, "MCATagSymbol", string("r1"))->add_child_text(buffer); if (!channel->MCATagName.empty()) { channel->MCATagName.get().EncodeString(buffer, sizeof(buffer)); - ch->add_child("MCATagName", "r1")->add_child_text(buffer); + cxml::add_child(ch, "MCATagName", string("r1"))->add_child_text(buffer); } if (!channel->MCAChannelID.empty()) { - ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get())); + cxml::add_child(ch, "MCAChannelID", string("r1"))->add_child_text(raw_convert<string>(channel->MCAChannelID.get())); } if (!channel->RFC5646SpokenLanguage.empty()) { channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer)); - ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer); + cxml::add_child(ch, "RFC5646SpokenLanguage", string("r1"))->add_child_text(buffer); } if (!channel->SoundfieldGroupLinkID.empty()) { channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer)); - ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer)); + cxml::add_child(ch, "SoundfieldGroupLinkID", string("r1"))->add_child_text("urn:uuid:" + string(buffer)); } } } @@ -481,16 +481,16 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m return; } - auto meta = node->add_child("meta:CompositionMetadataAsset"); + auto meta = cxml::add_child(node, "meta:CompositionMetadataAsset"); meta->set_namespace_declaration (cpl_metadata_ns, "meta"); - meta->add_child("Id")->add_child_text("urn:uuid:" + _cpl_metadata_id); + cxml::add_text_child(meta, "Id", "urn:uuid:" + _cpl_metadata_id); auto mp = _reels.front()->main_picture(); - meta->add_child("EditRate")->add_child_text(mp->edit_rate().as_string()); - meta->add_child("IntrinsicDuration")->add_child_text(raw_convert<string>(mp->intrinsic_duration())); + cxml::add_text_child(meta, "EditRate", mp->edit_rate().as_string()); + cxml::add_text_child(meta, "IntrinsicDuration", raw_convert<string>(mp->intrinsic_duration())); - auto fctt = meta->add_child("FullContentTitleText", "meta"); + auto fctt = cxml::add_child(meta, "FullContentTitleText", string("meta")); if (_full_content_title_text && !_full_content_title_text->empty()) { fctt->add_child_text (*_full_content_title_text); } @@ -499,11 +499,11 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m } if (_release_territory) { - meta->add_child("ReleaseTerritory", "meta")->add_child_text(*_release_territory); + cxml::add_child(meta, "ReleaseTerritory", string("meta"))->add_child_text(*_release_territory); } if (_version_number) { - xmlpp::Element* vn = meta->add_child("VersionNumber", "meta"); + auto vn = cxml::add_child(meta, "VersionNumber", string("meta")); vn->add_child_text(raw_convert<string>(*_version_number)); if (_status) { vn->set_attribute("status", status_to_string(*_status)); @@ -511,19 +511,19 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m } if (_chain) { - meta->add_child("Chain", "meta")->add_child_text(*_chain); + cxml::add_child(meta, "Chain", string("meta"))->add_child_text(*_chain); } if (_distributor) { - meta->add_child("Distributor", "meta")->add_child_text(*_distributor); + cxml::add_child(meta, "Distributor", string("meta"))->add_child_text(*_distributor); } if (_facility) { - meta->add_child("Facility", "meta")->add_child_text(*_facility); + cxml::add_child(meta, "Facility", string("meta"))->add_child_text(*_facility); } if (_content_versions.size() > 1) { - xmlpp::Element* vc = meta->add_child("AlternateContentVersionList", "meta"); + auto vc = cxml::add_child(meta, "AlternateContentVersionList", string("meta")); for (size_t i = 1; i < _content_versions.size(); ++i) { _content_versions[i].as_xml (vc); } @@ -534,17 +534,17 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m } if (_main_sound_configuration) { - meta->add_child("MainSoundConfiguration", "meta")->add_child_text(_main_sound_configuration->to_string()); + cxml::add_child(meta, "MainSoundConfiguration", string("meta"))->add_child_text(_main_sound_configuration->to_string()); } - meta->add_child("MainSoundSampleRate", "meta")->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1"); + cxml::add_child(meta, "MainSoundSampleRate", string("meta"))->add_child_text(raw_convert<string>(*_main_sound_sample_rate) + " 1"); - auto stored = meta->add_child("MainPictureStoredArea", "meta"); - stored->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->width)); - stored->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_stored_area->height)); + auto stored = cxml::add_child(meta, "MainPictureStoredArea", string("meta")); + cxml::add_child(stored, "Width", string("meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->width)); + cxml::add_child(stored, "Height", string("meta"))->add_child_text(raw_convert<string>(_main_picture_stored_area->height)); - auto active = meta->add_child("MainPictureActiveArea", "meta"); - active->add_child("Width", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->width)); - active->add_child("Height", "meta")->add_child_text(raw_convert<string>(_main_picture_active_area->height)); + auto active = cxml::add_child(meta, "MainPictureActiveArea", string("meta")); + cxml::add_child(active, "Width", string("meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->width)); + cxml::add_child(active, "Height", string("meta"))->add_child_text(raw_convert<string>(_main_picture_active_area->height)); optional<string> first_subtitle_language; for (auto i: _reels) { @@ -567,18 +567,18 @@ CPL::maybe_write_composition_metadata_asset(xmlpp::Element* node, bool include_m } lang += i; } - meta->add_child("MainSubtitleLanguageList", "meta")->add_child_text(lang); + cxml::add_child(meta, "MainSubtitleLanguageList", string("meta"))->add_child_text(lang); } - auto metadata_list = meta->add_child("ExtensionMetadataList", "meta"); + auto metadata_list = cxml::add_child(meta, "ExtensionMetadataList", string("meta")); auto add_extension_metadata = [metadata_list](string scope, string name, string property_name, string property_value) { - auto extension = metadata_list->add_child("ExtensionMetadata", "meta"); + auto extension = cxml::add_child(metadata_list, "ExtensionMetadata", string("meta")); extension->set_attribute("scope", scope); - extension->add_child("Name", "meta")->add_child_text(name); - auto property = extension->add_child("PropertyList", "meta")->add_child("Property", "meta"); - property->add_child("Name", "meta")->add_child_text(property_name); - property->add_child("Value", "meta")->add_child_text(property_value); + cxml::add_child(extension, "Name", string("meta"))->add_child_text(name); + auto property = cxml::add_child(cxml::add_child(extension, "PropertyList", string("meta")), "Property", string("meta")); + cxml::add_child(property, "Name", string("meta"))->add_child_text(property_name); + cxml::add_child(property, "Value", string("meta"))->add_child_text(property_value); }; /* SMPTE Bv2.1 8.6.3 */ @@ -441,7 +441,7 @@ DCP::write_volindex (Standard standard) const DCP_ASSERT (false); } - root->add_child("Index")->add_child_text ("1"); + cxml::add_text_child(root, "Index", "1"); doc.write_to_file_formatted(dcp::filesystem::fix_long_path(p).string(), "UTF-8"); } diff --git a/src/encrypted_kdm.cc b/src/encrypted_kdm.cc index 465a657d..d1089c0b 100644 --- a/src/encrypted_kdm.cc +++ b/src/encrypted_kdm.cc @@ -44,6 +44,7 @@ #include "file.h" #include "util.h" #include <libcxml/cxml.h> +#include <libxml++/attributenode.h> #include <libxml++/document.h> #include <libxml++/nodes/element.h> #include <libxml/parser.h> @@ -85,8 +86,8 @@ public: void as_xml (xmlpp::Element* node) const { - node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name); - node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number); + cxml::add_child(node, "X509IssuerName", string("ds"))->add_child_text(x509_issuer_name); + cxml::add_child(node, "X509SerialNumber", string("ds"))->add_child_text(x509_serial_number); } string x509_issuer_name; @@ -108,8 +109,8 @@ public: void as_xml (xmlpp::Element* node) const { - x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds")); - node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate); + x509_issuer_serial.as_xml(cxml::add_child(node, "X509IssuerSerial", string("ds"))); + cxml::add_child(node, "X509Certificate", string("ds"))->add_child_text(x509_certificate); } Signer x509_issuer_serial; @@ -136,8 +137,8 @@ public: void as_xml (xmlpp::Element* node) const { node->set_attribute ("URI", uri); - node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); - node->add_child("DigestValue", "ds")->add_child_text (digest_value); + cxml::add_child(node, "DigestMethod", string("ds"))->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + cxml::add_child(node, "DigestValue", string("ds"))->add_child_text(digest_value); } string uri; @@ -168,16 +169,16 @@ public: void as_xml (xmlpp::Element* node) const { - node->add_child ("CanonicalizationMethod", "ds")->set_attribute ( + cxml::add_child(node, "CanonicalizationMethod", string("ds"))->set_attribute( "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" ); - node->add_child ("SignatureMethod", "ds")->set_attribute ( + cxml::add_child(node, "SignatureMethod", string("ds"))->set_attribute( "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" ); - authenticated_public.as_xml (node->add_child ("Reference", "ds")); - authenticated_private.as_xml (node->add_child ("Reference", "ds")); + authenticated_public.as_xml(cxml::add_child(node, "Reference", string("ds"))); + authenticated_private.as_xml(cxml::add_child(node, "Reference", string("ds"))); } private: @@ -200,14 +201,14 @@ public: } } - void as_xml (xmlpp::Node* node) const + void as_xml(xmlpp::Element* element) const { - signed_info.as_xml (node->add_child ("SignedInfo", "ds")); - node->add_child("SignatureValue", "ds")->add_child_text (signature_value); + signed_info.as_xml(cxml::add_child(element, "SignedInfo", string("ds"))); + cxml::add_child(element, "SignatureValue", string("ds"))->add_child_text(signature_value); - auto key_info_node = node->add_child("KeyInfo", "ds"); + auto key_info_node = cxml::add_child(element, "KeyInfo", string("ds")); for (auto i: x509_data) { - i.as_xml (key_info_node->add_child("X509Data", "ds")); + i.as_xml(cxml::add_child(key_info_node, "X509Data", string("ds"))); } } @@ -229,22 +230,22 @@ public: } } - void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const + void as_xml (xmlpp::Element* node, map<string, xmlpp::AttributeNode*>& references) const { - references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate"); + references["ID_AuthenticatedPrivate"] = dynamic_cast<xmlpp::AttributeNode*>(node->set_attribute("Id", "ID_AuthenticatedPrivate")); for (auto i: encrypted_key) { - auto encrypted_key = node->add_child ("EncryptedKey", "enc"); + auto encrypted_key = cxml::add_child(node, "EncryptedKey", string("enc")); /* XXX: hack for testing with Dolby */ encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); - auto encryption_method = encrypted_key->add_child("EncryptionMethod", "enc"); + auto encryption_method = cxml::add_child(encrypted_key, "EncryptionMethod", string("enc")); encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); - auto digest_method = encryption_method->add_child ("DigestMethod", "ds"); + auto digest_method = cxml::add_child(encryption_method, "DigestMethod", string("ds")); /* XXX: hack for testing with Dolby */ digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); - auto cipher_data = encrypted_key->add_child("CipherData", "enc"); - cipher_data->add_child("CipherValue", "enc")->add_child_text (i); + auto cipher_data = cxml::add_child(encrypted_key, "CipherData", string("enc")); + cxml::add_child(cipher_data, "CipherValue", string("enc"))->add_child_text(i); } } @@ -271,9 +272,9 @@ public: void as_xml (xmlpp::Element* node) const { - auto type = node->add_child("KeyType"); + auto type = cxml::add_child(node, "KeyType"); type->add_child_text (key_type); - node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id); + cxml::add_text_child(node, "KeyId", "urn:uuid:" + key_id); /* XXX: this feels like a bit of a hack */ if (key_type == "MDEK") { type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type"); @@ -302,7 +303,7 @@ public: void as_xml (xmlpp::Element* node) const { for (auto const& i: typed_key_id) { - i.as_xml (node->add_child("TypedKeyId")); + i.as_xml(cxml::add_child(node, ("TypedKeyId"))); } } @@ -326,13 +327,13 @@ public: void as_xml (xmlpp::Element* node) const { - node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier); + cxml::add_text_child(node, "DeviceListIdentifier", "urn:uuid:" + device_list_identifier); if (device_list_description) { - node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get()); + cxml::add_text_child(node, "DeviceListDescription", device_list_description.get()); } - auto device_list = node->add_child ("DeviceList"); + auto device_list = cxml::add_child(node, "DeviceList"); for (auto i: certificate_thumbprints) { - device_list->add_child("CertificateThumbprint")->add_child_text (i); + cxml::add_text_child(device_list, "CertificateThumbprint", i); } } @@ -357,8 +358,8 @@ public: void as_xml (xmlpp::Element* node) const { - node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name); - node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number); + cxml::add_child(node, "X509IssuerName", string("ds"))->add_child_text(x509_issuer_name); + cxml::add_child(node, "X509SerialNumber", string("ds"))->add_child_text(x509_serial_number); } string x509_issuer_name; @@ -380,8 +381,8 @@ public: void as_xml (xmlpp::Element* node) const { - x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial")); - node->add_child("X509SubjectName")->add_child_text (x509_subject_name); + x509_issuer_serial.as_xml(cxml::add_child(node, "X509IssuerSerial")); + cxml::add_text_child(node, "X509SubjectName", x509_subject_name); } X509IssuerSerial x509_issuer_serial; @@ -428,30 +429,30 @@ public: { node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM"); - recipient.as_xml (node->add_child ("Recipient")); - node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id); - node->add_child("ContentTitleText")->add_child_text (content_title_text); + recipient.as_xml(cxml::add_child(node, "Recipient")); + cxml::add_text_child(node, "CompositionPlaylistId", "urn:uuid:" + composition_playlist_id); + cxml::add_text_child(node, "ContentTitleText", content_title_text); if (content_authenticator) { - node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ()); + cxml::add_text_child(node, "ContentAuthenticator", content_authenticator.get()); } - node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ()); - node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ()); + cxml::add_text_child(node, "ContentKeysNotValidBefore", not_valid_before.as_string()); + cxml::add_text_child(node, "ContentKeysNotValidAfter", not_valid_after.as_string()); if (authorized_device_info) { - authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo")); + authorized_device_info->as_xml(cxml::add_child(node, "AuthorizedDeviceInfo")); } - key_id_list.as_xml (node->add_child ("KeyIdList")); + key_id_list.as_xml(cxml::add_child(node, "KeyIdList")); if (disable_forensic_marking_picture || disable_forensic_marking_audio) { - auto forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList"); + auto forensic_mark_flag_list = cxml::add_child(node, "ForensicMarkFlagList"); if (disable_forensic_marking_picture) { - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable); + cxml::add_text_child(forensic_mark_flag_list, "ForensicMarkFlag", picture_disable); } if (disable_forensic_marking_audio) { auto mrkflg = audio_disable; if (*disable_forensic_marking_audio > 0) { mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio); } - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg); + cxml::add_text_child(forensic_mark_flag_list, "ForensicMarkFlag", mrkflg); } } } @@ -490,7 +491,7 @@ public: void as_xml (xmlpp::Element* node) const { - kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions")); + kdm_required_extensions.as_xml(cxml::add_child(node, "KDMRequiredExtensions")); } KDMRequiredExtensions kdm_required_extensions; @@ -517,21 +518,21 @@ public: } - void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const + void as_xml (xmlpp::Element* node, map<string, xmlpp::AttributeNode*>& references) const { - references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic"); + references["ID_AuthenticatedPublic"] = dynamic_cast<xmlpp::AttributeNode*>(node->set_attribute("Id", "ID_AuthenticatedPublic")); - node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id); - node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); + cxml::add_text_child(node, "MessageId", "urn:uuid:" + message_id); + cxml::add_text_child(node, "MessageType", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"); if (annotation_text) { - node->add_child("AnnotationText")->add_child_text (annotation_text.get ()); + cxml::add_text_child(node, "AnnotationText", annotation_text.get()); } - node->add_child("IssueDate")->add_child_text (issue_date); + cxml::add_text_child(node, "IssueDate", issue_date); - signer.as_xml (node->add_child ("Signer")); - required_extensions.as_xml (node->add_child ("RequiredExtensions")); + signer.as_xml(cxml::add_child(node, "Signer")); + required_extensions.as_xml(cxml::add_child(node, "RequiredExtensions")); - node->add_child ("NonCriticalExtensions"); + cxml::add_child(node, "NonCriticalExtensions"); } string message_id; @@ -563,14 +564,14 @@ public: shared_ptr<xmlpp::Document> as_xml () const { - shared_ptr<xmlpp::Document> document (new xmlpp::Document ()); - xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM"); + auto document = make_shared<xmlpp::Document>(); + auto root = document->create_root_node("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM"); root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); - map<string, xmlpp::Attribute *> references; - authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references); - authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references); - signature.as_xml (root->add_child ("Signature", "ds")); + map<string, xmlpp::AttributeNode*> references; + authenticated_public.as_xml(cxml::add_child(root, "AuthenticatedPublic"), references); + authenticated_private.as_xml(cxml::add_child(root, "AuthenticatedPrivate"), references); + signature.as_xml(cxml::add_child(root, "Signature", string("ds"))); for (auto i: references) { xmlAddID (0, document->cobj(), (const xmlChar *) i.first.c_str(), i.second->cobj()); diff --git a/src/interop_subtitle_asset.cc b/src/interop_subtitle_asset.cc index 32c3f66a..a4594207 100644 --- a/src/interop_subtitle_asset.cc +++ b/src/interop_subtitle_asset.cc @@ -115,13 +115,13 @@ InteropSubtitleAsset::xml_as_string () const auto root = doc.create_root_node ("DCSubtitle"); root->set_attribute ("Version", "1.0"); - root->add_child("SubtitleID")->add_child_text (_id); - root->add_child("MovieTitle")->add_child_text (_movie_title); - root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number)); - root->add_child("Language")->add_child_text (_language); + cxml::add_text_child(root, "SubtitleID", _id); + cxml::add_text_child(root, "MovieTitle", _movie_title); + cxml::add_text_child(root, "ReelNumber", raw_convert<string> (_reel_number)); + cxml::add_text_child(root, "Language", _language); for (auto i: _load_font_nodes) { - auto load_font = root->add_child("LoadFont"); + auto load_font = cxml::add_child(root, "LoadFont"); load_font->set_attribute ("Id", i->id); load_font->set_attribute ("URI", i->uri); } diff --git a/src/mono_picture_asset.cc b/src/mono_picture_asset.cc index a72fd7d4..9a0beb42 100644 --- a/src/mono_picture_asset.cc +++ b/src/mono_picture_asset.cc @@ -66,7 +66,8 @@ using namespace dcp; MonoPictureAsset::MonoPictureAsset (boost::filesystem::path file) : PictureAsset (file) { - ASDCP::JP2K::MXFReader reader; + Kumu::FileReaderFactory factory; + ASDCP::JP2K::MXFReader reader(factory); auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); @@ -109,14 +110,15 @@ MonoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& o return false; } - ASDCP::JP2K::MXFReader reader_A; + Kumu::FileReaderFactory factory; + ASDCP::JP2K::MXFReader reader_A(factory); DCP_ASSERT (_file); auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", _file->string(), r)); } - ASDCP::JP2K::MXFReader reader_B; + ASDCP::JP2K::MXFReader reader_B(factory); DCP_ASSERT (other->file ()); r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { @@ -105,26 +105,26 @@ PKL::write_xml (boost::filesystem::path file, shared_ptr<const CertificateChain> pkl = doc.create_root_node("PackingList", pkl_smpte_ns); } - pkl->add_child("Id")->add_child_text ("urn:uuid:" + _id); + cxml::add_text_child(pkl, "Id", "urn:uuid:" + _id); if (_annotation_text) { - pkl->add_child("AnnotationText")->add_child_text (*_annotation_text); + cxml::add_text_child(pkl, "AnnotationText", *_annotation_text); } - pkl->add_child("IssueDate")->add_child_text (_issue_date); - pkl->add_child("Issuer")->add_child_text (_issuer); - pkl->add_child("Creator")->add_child_text (_creator); + cxml::add_text_child(pkl, "IssueDate", _issue_date); + cxml::add_text_child(pkl, "Issuer", _issuer); + cxml::add_text_child(pkl, "Creator", _creator); - auto asset_list = pkl->add_child("AssetList"); + auto asset_list = cxml::add_child(pkl, "AssetList"); for (auto i: _assets) { - auto asset = asset_list->add_child("Asset"); - asset->add_child("Id")->add_child_text ("urn:uuid:" + i->id()); + auto asset = cxml::add_child(asset_list, "Asset"); + cxml::add_text_child(asset, "Id", "urn:uuid:" + i->id()); if (i->annotation_text()) { - asset->add_child("AnnotationText")->add_child_text (*i->annotation_text()); + cxml::add_text_child(asset, "AnnotationText", *i->annotation_text()); } - asset->add_child("Hash")->add_child_text (i->hash()); - asset->add_child("Size")->add_child_text (raw_convert<string>(i->size())); - asset->add_child("Type")->add_child_text (i->type()); + cxml::add_text_child(asset, "Hash", i->hash()); + cxml::add_text_child(asset, "Size", raw_convert<string>(i->size())); + cxml::add_text_child(asset, "Type", i->type()); if (auto filename = i->original_filename()) { - asset->add_child("OriginalFileName")->add_child_text(*filename); + cxml::add_text_child(asset, "OriginalFileName", *filename); } } diff --git a/src/rating.cc b/src/rating.cc index 21960365..156d5ad0 100644 --- a/src/rating.cc +++ b/src/rating.cc @@ -61,8 +61,8 @@ Rating::Rating (cxml::ConstNodePtr node) void Rating::as_xml (xmlpp::Element* parent) const { - parent->add_child("Agency")->add_child_text(agency); - parent->add_child("Label")->add_child_text(label); + cxml::add_text_child(parent, "Agency", agency); + cxml::add_text_child(parent, "Label", label); } diff --git a/src/reel.cc b/src/reel.cc index a8481d59..def3a4ae 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -141,9 +141,9 @@ Reel::Reel (std::shared_ptr<const cxml::Node> node, dcp::Standard standard) xmlpp::Element * Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const { - auto reel = node->add_child ("Reel"); - reel->add_child("Id")->add_child_text("urn:uuid:" + _id); - xmlpp::Element* asset_list = reel->add_child ("AssetList"); + auto reel = cxml::add_child(node, "Reel"); + cxml::add_text_child(reel, "Id", "urn:uuid:" + _id); + auto asset_list = cxml::add_child(reel, "AssetList"); if (_main_markers) { _main_markers->write_to_cpl (asset_list, standard); diff --git a/src/reel_asset.cc b/src/reel_asset.cc index 3a3ae731..c782cf2b 100644 --- a/src/reel_asset.cc +++ b/src/reel_asset.cc @@ -84,10 +84,10 @@ ReelAsset::ReelAsset (shared_ptr<const cxml::Node> node) } -xmlpp::Node* -ReelAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { - auto a = node->add_child (cpl_node_name (standard)); + auto a = cxml::add_child(node, cpl_node_name(standard)); auto const attr = cpl_node_attribute (standard); if (!attr.first.empty ()) { a->set_attribute (attr.first, attr.second); @@ -96,18 +96,18 @@ ReelAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const if (!ns.first.empty()) { a->set_namespace_declaration (ns.first, ns.second); } - a->add_child("Id")->add_child_text ("urn:uuid:" + _id); + cxml::add_text_child(a, "Id", "urn:uuid:" + _id); /* Empty <AnnotationText> tags cause refusal to play on some Sony SRX320 / LMT3000 systems (DoM bug #2124) */ if (_annotation_text && !_annotation_text->empty()) { - a->add_child("AnnotationText")->add_child_text(*_annotation_text); + cxml::add_text_child(a, "AnnotationText", *_annotation_text); } - a->add_child("EditRate")->add_child_text (_edit_rate.as_string()); - a->add_child("IntrinsicDuration")->add_child_text (raw_convert<string> (_intrinsic_duration)); + cxml::add_text_child(a, "EditRate", _edit_rate.as_string()); + cxml::add_text_child(a, "IntrinsicDuration", raw_convert<string>(_intrinsic_duration)); if (_entry_point) { - a->add_child("EntryPoint")->add_child_text(raw_convert<string>(*_entry_point)); + cxml::add_text_child(a, "EntryPoint", raw_convert<string>(*_entry_point)); } if (_duration) { - a->add_child("Duration")->add_child_text(raw_convert<string>(*_duration)); + cxml::add_text_child(a, "Duration", raw_convert<string>(*_duration)); } return a; } diff --git a/src/reel_asset.h b/src/reel_asset.h index e928cb18..8dad739e 100644 --- a/src/reel_asset.h +++ b/src/reel_asset.h @@ -83,7 +83,7 @@ public: explicit ReelAsset (std::shared_ptr<const cxml::Node>); - virtual xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const; + virtual xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const; virtual bool encryptable () const { return false; diff --git a/src/reel_atmos_asset.cc b/src/reel_atmos_asset.cc index ef39a4eb..c2cdb7f3 100644 --- a/src/reel_atmos_asset.cc +++ b/src/reel_atmos_asset.cc @@ -82,11 +82,11 @@ ReelAtmosAsset::cpl_node_namespace () const } -xmlpp::Node * -ReelAtmosAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelAtmosAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { auto asset = ReelFileAsset::write_to_cpl (node, standard); - asset->add_child("axd:DataType")->add_child_text("urn:smpte:ul:060e2b34.04010105.0e090604.00000000"); + cxml::add_text_child(asset, "axd:DataType", "urn:smpte:ul:060e2b34.04010105.0e090604.00000000"); return asset; } diff --git a/src/reel_atmos_asset.h b/src/reel_atmos_asset.h index 298cbbfd..c3de76a8 100644 --- a/src/reel_atmos_asset.h +++ b/src/reel_atmos_asset.h @@ -68,7 +68,7 @@ public: return asset_of_type<AtmosAsset>(); } - xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; bool equals(std::shared_ptr<const ReelAtmosAsset>, EqualityOptions const&, NoteHandler) const; private: diff --git a/src/reel_file_asset.cc b/src/reel_file_asset.cc index 5fefda27..8fed8012 100644 --- a/src/reel_file_asset.cc +++ b/src/reel_file_asset.cc @@ -94,15 +94,15 @@ ReelFileAsset::file_asset_equals(shared_ptr<const ReelFileAsset> other, Equality } -xmlpp::Node * -ReelFileAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelFileAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { - auto asset = ReelAsset::write_to_cpl (node, standard); + auto asset = ReelAsset::write_to_cpl(node, standard); if (_key_id) { - asset->add_child("KeyId")->add_child_text("urn:uuid:" + *_key_id); + cxml::add_text_child(asset, "KeyId", "urn:uuid:" + *_key_id); } if (_hash) { - asset->add_child("Hash")->add_child_text(*_hash); + cxml::add_text_child(asset, "Hash", *_hash); } return asset; } diff --git a/src/reel_file_asset.h b/src/reel_file_asset.h index 687e0d3e..48fdf215 100644 --- a/src/reel_file_asset.h +++ b/src/reel_file_asset.h @@ -56,7 +56,7 @@ public: ReelFileAsset (std::shared_ptr<Asset> asset, boost::optional<std::string> key_id, std::string id, Fraction edit_rate, int64_t intrinsic_duration, int64_t entry_point); explicit ReelFileAsset (std::shared_ptr<const cxml::Node> node); - virtual xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + virtual xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; /** @return a Ref to our actual asset */ Ref const & asset_ref () const { diff --git a/src/reel_interop_closed_caption_asset.cc b/src/reel_interop_closed_caption_asset.cc index be968068..c4539fd7 100644 --- a/src/reel_interop_closed_caption_asset.cc +++ b/src/reel_interop_closed_caption_asset.cc @@ -75,12 +75,12 @@ ReelInteropClosedCaptionAsset::cpl_node_namespace () const } -xmlpp::Node * -ReelInteropClosedCaptionAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelInteropClosedCaptionAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { auto asset = ReelClosedCaptionAsset::write_to_cpl (node, standard); if (_language) { - asset->add_child("Language")->add_child_text(*_language); + cxml::add_text_child(asset, "Language", *_language); } return asset; } diff --git a/src/reel_interop_closed_caption_asset.h b/src/reel_interop_closed_caption_asset.h index 5e8f7c1e..5a074357 100644 --- a/src/reel_interop_closed_caption_asset.h +++ b/src/reel_interop_closed_caption_asset.h @@ -62,7 +62,7 @@ public: return asset_of_type<InteropSubtitleAsset>(); } - xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; private: std::string cpl_node_name (Standard) const override; diff --git a/src/reel_markers_asset.cc b/src/reel_markers_asset.cc index d71a22ec..4b1b3472 100644 --- a/src/reel_markers_asset.cc +++ b/src/reel_markers_asset.cc @@ -104,16 +104,16 @@ ReelMarkersAsset::get (Marker m) const } -xmlpp::Node* -ReelMarkersAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelMarkersAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { int const tcr = edit_rate().numerator / edit_rate().denominator; auto asset = ReelAsset::write_to_cpl (node, standard); - auto ml = asset->add_child("MarkerList"); + auto ml = cxml::add_child(asset, "MarkerList"); for (auto const& i: _markers) { - auto m = ml->add_child("Marker"); - m->add_child("Label")->add_child_text(marker_to_string(i.first)); - m->add_child("Offset")->add_child_text(raw_convert<string>(i.second.as_editable_units_ceil(tcr))); + auto m = cxml::add_child(ml, "Marker"); + cxml::add_text_child(m, "Label", marker_to_string(i.first)); + cxml::add_text_child(m, "Offset", raw_convert<string>(i.second.as_editable_units_ceil(tcr))); } return asset; diff --git a/src/reel_markers_asset.h b/src/reel_markers_asset.h index 2a1480a2..1e87957a 100644 --- a/src/reel_markers_asset.h +++ b/src/reel_markers_asset.h @@ -51,7 +51,7 @@ public: ReelMarkersAsset (Fraction edit_rate, int64_t intrinsic_duration); explicit ReelMarkersAsset (std::shared_ptr<const cxml::Node>); - xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; bool equals(std::shared_ptr<const ReelMarkersAsset>, EqualityOptions const&, NoteHandler) const; void set (Marker, Time); diff --git a/src/reel_picture_asset.cc b/src/reel_picture_asset.cc index eb87d039..7ee5fa38 100644 --- a/src/reel_picture_asset.cc +++ b/src/reel_picture_asset.cc @@ -86,12 +86,12 @@ ReelPictureAsset::ReelPictureAsset (shared_ptr<const cxml::Node> node) } -xmlpp::Node* -ReelPictureAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelPictureAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { auto asset = ReelFileAsset::write_to_cpl (node, standard); - asset->add_child("FrameRate")->add_child_text(String::compose("%1 %2", _frame_rate.numerator, _frame_rate.denominator)); + cxml::add_text_child(asset, "FrameRate", String::compose("%1 %2", _frame_rate.numerator, _frame_rate.denominator)); if (standard == Standard::INTEROP) { @@ -113,10 +113,12 @@ ReelPictureAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const } } - asset->add_child("ScreenAspectRatio")->add_child_text(raw_convert<string>(closest.get(), 2, true)); + cxml::add_text_child(asset, "ScreenAspectRatio", raw_convert<string>(closest.get(), 2, true)); } else { - asset->add_child("ScreenAspectRatio")->add_child_text( - String::compose ("%1 %2", _screen_aspect_ratio.numerator, _screen_aspect_ratio.denominator) + cxml::add_text_child( + asset, + "ScreenAspectRatio", + String::compose("%1 %2", _screen_aspect_ratio.numerator, _screen_aspect_ratio.denominator) ); } diff --git a/src/reel_picture_asset.h b/src/reel_picture_asset.h index bf7d40aa..ebfef744 100644 --- a/src/reel_picture_asset.h +++ b/src/reel_picture_asset.h @@ -67,7 +67,7 @@ public: return asset_of_type<PictureAsset>(); } - virtual xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + virtual xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; bool equals(std::shared_ptr<const ReelPictureAsset>, EqualityOptions const&, NoteHandler) const; /** @return picture frame rate */ diff --git a/src/reel_smpte_closed_caption_asset.cc b/src/reel_smpte_closed_caption_asset.cc index a2a68202..70e5eb36 100644 --- a/src/reel_smpte_closed_caption_asset.cc +++ b/src/reel_smpte_closed_caption_asset.cc @@ -65,12 +65,12 @@ ReelSMPTEClosedCaptionAsset::ReelSMPTEClosedCaptionAsset (shared_ptr<const cxml: } -xmlpp::Node * -ReelSMPTEClosedCaptionAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element* +ReelSMPTEClosedCaptionAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { auto asset = ReelClosedCaptionAsset::write_to_cpl (node, standard); if (_language) { - asset->add_child("Language", "tt")->add_child_text(*_language); + cxml::add_child(asset, "Language", string("tt"))->add_child_text(*_language); } return asset; } diff --git a/src/reel_smpte_closed_caption_asset.h b/src/reel_smpte_closed_caption_asset.h index 32a79efd..e7a26f65 100644 --- a/src/reel_smpte_closed_caption_asset.h +++ b/src/reel_smpte_closed_caption_asset.h @@ -63,7 +63,7 @@ public: return asset_of_type<const SMPTESubtitleAsset>(); } - xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; private: diff --git a/src/reel_subtitle_asset.cc b/src/reel_subtitle_asset.cc index d856a05e..436aa69f 100644 --- a/src/reel_subtitle_asset.cc +++ b/src/reel_subtitle_asset.cc @@ -103,12 +103,12 @@ ReelSubtitleAsset::equals(shared_ptr<const ReelSubtitleAsset> other, EqualityOpt } -xmlpp::Node * -ReelSubtitleAsset::write_to_cpl (xmlpp::Node* node, Standard standard) const +xmlpp::Element * +ReelSubtitleAsset::write_to_cpl(xmlpp::Element* node, Standard standard) const { auto asset = ReelFileAsset::write_to_cpl (node, standard); if (_language) { - asset->add_child("Language")->add_child_text(*_language); + cxml::add_text_child(asset, "Language", *_language); } return asset; } diff --git a/src/reel_subtitle_asset.h b/src/reel_subtitle_asset.h index 8b694fd6..c619c752 100644 --- a/src/reel_subtitle_asset.h +++ b/src/reel_subtitle_asset.h @@ -73,7 +73,7 @@ public: return asset_of_type<SubtitleAsset>(); } - xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + xmlpp::Element* write_to_cpl(xmlpp::Element* node, Standard standard) const override; bool equals(std::shared_ptr<const ReelSubtitleAsset>, EqualityOptions const&, NoteHandler) const; diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index 0ff1d7ef..4f611583 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -95,7 +95,8 @@ SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) { auto xml = make_shared<cxml::Document>("SubtitleReel"); - auto reader = make_shared<ASDCP::TimedText::MXFReader>(); + Kumu::FileReaderFactory factory; + auto reader = make_shared<ASDCP::TimedText::MXFReader>(factory); auto r = Kumu::RESULT_OK; { ASDCPErrorSuspender sus; @@ -320,7 +321,8 @@ SMPTESubtitleAsset::set_key (Key key) /* Our data was encrypted; now we can decrypt it */ - auto reader = make_shared<ASDCP::TimedText::MXFReader>(); + Kumu::FileReaderFactory factory; + auto reader = make_shared<ASDCP::TimedText::MXFReader>(factory); auto r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception ( @@ -354,7 +356,8 @@ SMPTESubtitleAsset::load_font_nodes () const bool SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file) { - ASDCP::TimedText::MXFReader reader; + Kumu::FileReaderFactory factory; + ASDCP::TimedText::MXFReader reader(factory); Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL); auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL); @@ -369,31 +372,31 @@ SMPTESubtitleAsset::xml_as_string () const auto root = doc.create_root_node ("SubtitleReel"); DCP_ASSERT (_xml_id); - root->add_child("Id")->add_child_text("urn:uuid:" + *_xml_id); - root->add_child("ContentTitleText")->add_child_text(_content_title_text); + cxml::add_text_child(root, "Id", "urn:uuid:" + *_xml_id); + cxml::add_text_child(root, "ContentTitleText", _content_title_text); if (_annotation_text) { - root->add_child("AnnotationText")->add_child_text(_annotation_text.get()); + cxml::add_text_child(root, "AnnotationText", _annotation_text.get()); } - root->add_child("IssueDate")->add_child_text(_issue_date.as_string(false, false)); + cxml::add_text_child(root, "IssueDate", _issue_date.as_string(false, false)); if (_reel_number) { - root->add_child("ReelNumber")->add_child_text(raw_convert<string>(_reel_number.get())); + cxml::add_text_child(root, "ReelNumber", raw_convert<string>(_reel_number.get())); } if (_language) { - root->add_child("Language")->add_child_text(_language.get()); + cxml::add_text_child(root, "Language", _language.get()); } - root->add_child("EditRate")->add_child_text(_edit_rate.as_string()); - root->add_child("TimeCodeRate")->add_child_text(raw_convert<string>(_time_code_rate)); + cxml::add_text_child(root, "EditRate", _edit_rate.as_string()); + cxml::add_text_child(root, "TimeCodeRate", raw_convert<string>(_time_code_rate)); if (_start_time) { - root->add_child("StartTime")->add_child_text(_start_time.get().as_string(Standard::SMPTE)); + cxml::add_text_child(root, "StartTime", _start_time.get().as_string(Standard::SMPTE)); } for (auto i: _load_font_nodes) { - auto load_font = root->add_child("LoadFont"); + auto load_font = cxml::add_child(root, "LoadFont"); load_font->add_child_text ("urn:uuid:" + i->urn); load_font->set_attribute ("ID", i->id); } - subtitles_as_xml (root->add_child("SubtitleList"), _time_code_rate, Standard::SMPTE); + subtitles_as_xml(cxml::add_child(root, "SubtitleList"), _time_code_rate, Standard::SMPTE); return format_xml(doc, std::make_pair(string{}, schema_namespace())); } diff --git a/src/sound_asset.cc b/src/sound_asset.cc index 0ceba53d..90075278 100644 --- a/src/sound_asset.cc +++ b/src/sound_asset.cc @@ -70,7 +70,8 @@ using namespace dcp; SoundAsset::SoundAsset (boost::filesystem::path file) : Asset (file) { - ASDCP::PCM::MXFReader reader; + Kumu::FileReaderFactory factory; + ASDCP::PCM::MXFReader reader(factory); auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); @@ -138,14 +139,15 @@ SoundAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, No return true; } - ASDCP::PCM::MXFReader reader_A; + Kumu::FileReaderFactory factory; + ASDCP::PCM::MXFReader reader_A(factory); DCP_ASSERT (file()); auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*file()).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r)); } - ASDCP::PCM::MXFReader reader_B; + ASDCP::PCM::MXFReader reader_B(factory); r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r)); @@ -278,7 +280,8 @@ SoundAsset::static_pkl_type (Standard standard) bool SoundAsset::valid_mxf (boost::filesystem::path file) { - ASDCP::PCM::MXFReader reader; + Kumu::FileReaderFactory factory; + ASDCP::PCM::MXFReader reader(factory); Kumu::Result_t r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); return !ASDCP_FAILURE (r); } diff --git a/src/stereo_picture_asset.cc b/src/stereo_picture_asset.cc index 2ce3cdc9..687d8572 100644 --- a/src/stereo_picture_asset.cc +++ b/src/stereo_picture_asset.cc @@ -59,7 +59,8 @@ using namespace dcp; StereoPictureAsset::StereoPictureAsset (boost::filesystem::path file) : PictureAsset (file) { - ASDCP::JP2K::MXFSReader reader; + Kumu::FileReaderFactory factory; + ASDCP::JP2K::MXFSReader reader(factory); auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r)); @@ -105,14 +106,15 @@ StereoPictureAsset::start_read () const bool StereoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const { - ASDCP::JP2K::MXFSReader reader_A; + Kumu::FileReaderFactory factory; + ASDCP::JP2K::MXFSReader reader_A(factory); DCP_ASSERT (file()); auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*file()).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not open MXF file for reading", file()->string(), r)); } - ASDCP::JP2K::MXFSReader reader_B; + ASDCP::JP2K::MXFSReader reader_B(factory); DCP_ASSERT (other->file()); r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str()); if (ASDCP_FAILURE (r)) { diff --git a/src/subtitle_asset_internal.cc b/src/subtitle_asset_internal.cc index 99d8411b..39f68624 100644 --- a/src/subtitle_asset_internal.cc +++ b/src/subtitle_asset_internal.cc @@ -77,7 +77,7 @@ order::Font::Font (shared_ptr<SubtitleString> s, Standard standard) xmlpp::Element* order::Font::as_xml (xmlpp::Element* parent, Context&) const { - auto e = parent->add_child("Font"); + auto e = cxml::add_child(parent, "Font"); for (const auto& i: _values) { e->set_attribute (i.first, i.second); } @@ -137,7 +137,7 @@ xmlpp::Element* order::String::as_xml (xmlpp::Element* parent, Context& context) const { if (fabs(_space_before) > SPACE_BEFORE_EPSILON) { - auto space = parent->add_child("Space"); + auto space = cxml::add_child(parent, "Space"); auto size = raw_convert<string>(_space_before, 2); if (context.standard == Standard::INTEROP) { size += "em"; @@ -212,7 +212,7 @@ position_align (xmlpp::Element* e, order::Context& context, HAlign h_align, floa xmlpp::Element* order::Text::as_xml (xmlpp::Element* parent, Context& context) const { - auto e = parent->add_child ("Text"); + auto e = cxml::add_child(parent, "Text"); position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position); @@ -224,9 +224,9 @@ order::Text::as_xml (xmlpp::Element* parent, Context& context) const } for (auto const& ruby: _rubies) { - auto xml = e->add_child("Ruby"); - xml->add_child("Rb")->add_child_text(ruby.base); - auto rt = xml->add_child("Rt"); + auto xml = cxml::add_child(e, "Ruby"); + cxml::add_child(xml, "Rb")->add_child_text(ruby.base); + auto rt = cxml::add_child(xml, "Rt"); rt->add_child_text(ruby.annotation); rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6)); rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after"); @@ -242,7 +242,7 @@ order::Text::as_xml (xmlpp::Element* parent, Context& context) const xmlpp::Element* order::Subtitle::as_xml (xmlpp::Element* parent, Context& context) const { - auto e = parent->add_child ("Subtitle"); + auto e = cxml::add_child(parent, "Subtitle"); e->set_attribute ("SpotNumber", raw_convert<string> (context.spot_number++)); e->set_attribute ("TimeIn", _in.rebase(context.time_code_rate).as_string(context.standard)); e->set_attribute ("TimeOut", _out.rebase(context.time_code_rate).as_string(context.standard)); @@ -274,7 +274,7 @@ order::Font::clear () xmlpp::Element * order::Image::as_xml (xmlpp::Element* parent, Context& context) const { - auto e = parent->add_child ("Image"); + auto e = cxml::add_child(parent, "Image"); position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position); if (context.standard == Standard::SMPTE) { diff --git a/src/types.cc b/src/types.cc index f4bf8db0..c4ba8886 100644 --- a/src/types.cc +++ b/src/types.cc @@ -310,9 +310,9 @@ ContentVersion::ContentVersion (string label_text_) void ContentVersion::as_xml (xmlpp::Element* parent) const { - auto cv = parent->add_child("ContentVersion"); - cv->add_child("Id")->add_child_text(id); - cv->add_child("LabelText")->add_child_text(label_text); + auto cv = cxml::add_child(parent, "ContentVersion"); + cxml::add_text_child(cv, "Id", id); + cxml::add_text_child(cv, "LabelText", label_text); } @@ -345,7 +345,7 @@ Luminance::set_value (float v) void Luminance::as_xml (xmlpp::Element* parent, string ns) const { - auto lum = parent->add_child("Luminance", ns); + auto lum = cxml::add_child(parent, "Luminance", ns); lum->set_attribute("units", unit_to_string(_unit)); lum->add_child_text(raw_convert<string>(_value, 3)); } diff --git a/src/verify.cc b/src/verify.cc index 112a5bb5..cfcce04f 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -88,6 +88,7 @@ using std::cout; using std::dynamic_pointer_cast; +using std::function; using std::list; using std::make_shared; using std::map; @@ -97,7 +98,6 @@ using std::shared_ptr; using std::string; using std::vector; using boost::optional; -using boost::function; using namespace dcp; @@ -293,9 +293,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 (); @@ -348,7 +422,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 { @@ -366,13 +440,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() - }); + ); } } @@ -386,9 +459,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 ) @@ -402,11 +474,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()); @@ -436,47 +508,54 @@ 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 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; } }; @@ -484,55 +563,51 @@ verify_picture_asset(shared_ptr<const ReelFileAsset> reel_file_asset, boost::fil 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)) { 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, @@ -541,17 +616,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 ( @@ -559,12 +633,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 */ @@ -572,69 +641,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, + 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, @@ -643,58 +693,58 @@ 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); } } 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()); } } @@ -702,22 +752,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" @@ -729,54 +777,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); } } @@ -784,14 +826,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()) { @@ -801,7 +843,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); } } } @@ -809,23 +851,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) { @@ -841,19 +876,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()); } } } @@ -862,35 +897,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); } } @@ -899,10 +932,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, @@ -996,7 +1028,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; } @@ -1007,7 +1039,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); @@ -1030,8 +1062,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]))); } } @@ -1040,47 +1072,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) { @@ -1146,7 +1165,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; } @@ -1169,15 +1188,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); } } @@ -1283,14 +1298,14 @@ 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()); }, @@ -1307,7 +1322,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(); }, @@ -1323,12 +1338,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"); @@ -1378,9 +1393,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()); } } @@ -1413,17 +1428,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, @@ -1433,24 +1441,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; } } @@ -1467,32 +1475,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() + ); } } } @@ -1500,13 +1502,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 { @@ -1514,9 +1516,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()); } } @@ -1525,10 +1527,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); } } @@ -1540,26 +1542,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") { @@ -1571,61 +1568,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 */ @@ -1639,7 +1646,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); } } @@ -1654,38 +1663,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, @@ -1695,51 +1705,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)); } } @@ -1751,12 +1760,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(); @@ -1769,26 +1778,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()); } } } @@ -1797,27 +1806,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; } } @@ -1827,28 +1831,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, @@ -1864,7 +1864,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) { @@ -1873,22 +1873,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) { @@ -1905,16 +1908,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())}); } @@ -1922,23 +1918,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.). * @@ -1950,238 +1946,267 @@ 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::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 ""; @@ -2202,12 +2227,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()) { diff --git a/src/verify.h b/src/verify.h index b5d913bd..b9e3623f 100644 --- a/src/verify.h +++ b/src/verify.h @@ -58,16 +58,20 @@ namespace dcp { +class DCP; + + class VerificationNote { public: enum class Type { + OK, ERROR, BV21_ERROR, ///< may not always be considered an error, but violates a "shall" requirement of Bv2.1 WARNING }; - /** Codes for errors or warnings from verifying DCPs. + /** Codes for successful checks, errors or warnings from verifying DCPs. * * The names should (in general) answer the question "what is wrong?" with an answer that begins "There is a ..." * e.g. "There is a INCORRECT_CPL_HASH" @@ -98,6 +102,7 @@ public: * note contains (probably technical) details */ FAILED_READ, + MATCHING_CPL_HASHES, /** The hash of the CPL in the PKL does not agree with the CPL file * note contains CPL ID * file contains CPL filename @@ -109,6 +114,7 @@ public: * note contains the invalid frame rate as "<numerator>/<denominator>" */ INVALID_PICTURE_FRAME_RATE, + CORRECT_PICTURE_HASH, /** The hash of a main picture asset does not agree with the PKL file * file contains the picture asset filename * calculated_hash contains the current hash of the picture MXF @@ -153,6 +159,7 @@ public: * note contains asset ID */ INVALID_DURATION, + VALID_PICTURE_FRAME_SIZES_IN_BYTES, /** The JPEG2000 data in at least one picture frame is larger than the equivalent of 250Mbit/s * file contains the picture asset filename */ @@ -175,6 +182,7 @@ public: * note contains the invalid language */ INVALID_LANGUAGE, + VALID_RELEASE_TERRITORY, /** A picture asset does not have one of the required Bv2.1 sizes (in pixels) [Bv2.1_7.1] * note contains the incorrect size as "<width>x<height>" * file contains the asset filename @@ -257,6 +265,7 @@ public: * file contains the CPL filename */ MISMATCHED_CPL_ANNOTATION_TEXT, + VALID_CPL_ANNOTATION_TEXT, /** At least one asset in a reel does not have the same duration as the others */ MISMATCHED_ASSET_DURATION, /** If one reel has a _MainSubtitle_, all must have them */ @@ -334,6 +343,11 @@ public: * file contains the PKL filename */ MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, + MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL, + /** All content is encrypted */ + ALL_ENCRYPTED, + /** No content is encrypted */ + NONE_ENCRYPTED, /** Some, but not all content, is encrypted */ PARTIALLY_ENCRYPTED, /** General error during JPEG2000 codestream verification @@ -406,11 +420,13 @@ public: UNEXPECTED_DURATION, /** A <ContentKind> has been specified with either no scope or the SMPTE 429-7 scope, but which is not one of those allowed */ INVALID_CONTENT_KIND, + VALID_CONTENT_KIND, /** Either the width or height of a <MainPictureActiveArea> in a CPL is either not an even number, or bigger than the corresponding asset dimension. * note contains details of what is wrong * file contains the CPL filename */ INVALID_MAIN_PICTURE_ACTIVE_AREA, + VALID_MAIN_PICTURE_ACTIVE_AREA, /** A PKL has more than one asset with the same ID * note contains the PKL ID * file contains the PKL filename @@ -475,13 +491,13 @@ public: * file contains the CPL filename */ EMPTY_CONTENT_VERSION_LABEL_TEXT, + VALID_CONTENT_VERSION_LABEL_TEXT, /** The CPL namespace is not valid. * note contains the invalid namespace * file contains the CPL filename */ INVALID_CPL_NAMESPACE, /** A SMPTE CPL does not contain a _<ContentVersion>_ tag - * note contains the CPL ID * file contains the CPL filename */ MISSING_CPL_CONTENT_VERSION @@ -542,6 +558,7 @@ private: ID, OTHER_ID, FRAME_RATE, + CPL_ID, CALCULATED_HASH, REFERENCE_HASH }; @@ -641,6 +658,15 @@ public: return data<std::string>(Data::REFERENCE_HASH); } + VerificationNote& set_cpl_id(std::string id) { + _data[Data::CPL_ID] = id; + return *this; + } + + boost::optional<std::string> cpl_id() const { + return data<std::string>(Data::CPL_ID); + } + private: Type _type; Code _code; @@ -658,18 +684,30 @@ struct VerificationOptions }; -std::vector<VerificationNote> verify ( +struct VerificationResult +{ + std::vector<VerificationNote> notes; + std::vector<std::shared_ptr<dcp::DCP>> dcps; +}; + + +VerificationResult verify( std::vector<boost::filesystem::path> directories, std::vector<dcp::DecryptedKDM> kdms, - boost::function<void (std::string, boost::optional<boost::filesystem::path>)> stage, - boost::function<void (float)> progress, + std::function<void (std::string, boost::optional<boost::filesystem::path>)> stage, + std::function<void (float)> progress, VerificationOptions options = {}, boost::optional<boost::filesystem::path> xsd_dtd_directory = boost::optional<boost::filesystem::path>() ); -std::string note_to_string (dcp::VerificationNote note); +std::string note_to_string( + dcp::VerificationNote note, + std::function<std::string (std::string)> process_string = [](std::string s) { return s; }, + std::function<std::string (std::string)> process_filename = [](std::string s) { return s; } + ); bool operator== (dcp::VerificationNote const& a, dcp::VerificationNote const& b); +bool operator!=(dcp::VerificationNote const& a, dcp::VerificationNote const& b); bool operator< (dcp::VerificationNote const& a, dcp::VerificationNote const& b); std::ostream& operator<<(std::ostream& s, dcp::VerificationNote const& note); diff --git a/src/verify_report.cc b/src/verify_report.cc new file mode 100644 index 00000000..2201b8fa --- /dev/null +++ b/src/verify_report.cc @@ -0,0 +1,144 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + This file is part of libdcp. + + libdcp is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + libdcp is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libdcp. If not, see <http://www.gnu.org/licenses/>. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. +*/ + + +#include "compose.hpp" +#include "cpl.h" +#include "dcp.h" +#include "file.h" +#include "reel.h" +#include "reel_picture_asset.h" +#include "reel_sound_asset.h" +#include "reel_subtitle_asset.h" +#include "verify.h" +#include "verify_report.h" + + +using std::shared_ptr; +using std::string; +using std::vector; +using boost::optional; +using namespace dcp; + + +void write_line(File& file, string format) +{ + file.puts(string(format + "\n").c_str()); +} + + +template <typename... Args> +void write_line(File& file, string format, Args... args) +{ + file.puts(String::compose(format + "\n", std::forward<Args>(args)...).c_str()); +} + + +void +dcp::verify_report(dcp::VerificationResult const& result, Formatter& formatter) +{ + auto document = formatter.document(); + auto body = formatter.body(); + + formatter.heading("DCP verification report"); + + if (result.dcps.size() > 1) { + formatter.subheading("DCPs"); + } else { + formatter.subheading("DCP"); + } + + auto reel_asset_details = [&formatter](shared_ptr<dcp::ReelAsset> asset) { + formatter.list_item(String::compose("UUID: %1", asset->id())); + formatter.list_item(String::compose("Intrinsic duration: %1", asset->intrinsic_duration())); + formatter.list_item(String::compose("Entry point: %1", asset->entry_point().get_value_or(0))); + formatter.list_item(String::compose("Duration: %1", asset->duration().get_value_or(0))); + if (asset->annotation_text()) { + formatter.list_item(String::compose("Annotation text: %1", *asset->annotation_text())); + } + }; + + auto write_notes = [&formatter](dcp::VerificationResult const& result, optional<string> cpl_id) { + for (auto note: result.notes) { + if (note.cpl_id() == cpl_id) { + auto const note_as_string = dcp::note_to_string(note, formatter.process_string(), formatter.process_filename()); + if (note.type() == dcp::VerificationNote::Type::OK) { + formatter.list_item(note_as_string, string("ok")); + } else if (note.type() == dcp::VerificationNote::Type::WARNING) { + formatter.list_item(note_as_string, string("warning")); + } else if (note.type() == dcp::VerificationNote::Type::ERROR) { + formatter.list_item(note_as_string, string("error")); + } + } + } + }; + + for (auto dcp: result.dcps) { + auto ul = formatter.unordered_list(); + for (auto cpl: dcp->cpls()) { + formatter.list_item(String::compose("CPL ID: %1", cpl->id())); + int reel_index = 1; + for (auto reel: cpl->reels()) { + formatter.list_item(String::compose("Reel: %1", reel_index++)); + auto ul2 = formatter.unordered_list(); + if (auto pic = reel->main_picture()) { + formatter.list_item("Main picture"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(pic); + formatter.list_item(String::compose("Frame rate: %1", pic->frame_rate().numerator)); + formatter.list_item(String::compose("Screen aspect ratio: %1x%2", pic->screen_aspect_ratio().numerator, pic->screen_aspect_ratio().denominator)); + } + if (auto sound = reel->main_sound()) { + formatter.list_item("Main sound"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(sound); + } + if (auto sub = reel->main_subtitle()) { + formatter.list_item("Main subtitle"); + auto ul3 = formatter.unordered_list(); + reel_asset_details(sub); + if (sub->language()) { + formatter.list_item(String::compose("Language: %1", *sub->language())); + } + } + } + write_notes(result, cpl->id()); + } + } + + if (std::count_if(result.notes.begin(), result.notes.end(), [](VerificationNote const& note) { return !note.cpl_id(); }) > 0) { + formatter.subheading("Report"); + write_notes(result, {}); + } +} + diff --git a/src/verify_report.h b/src/verify_report.h new file mode 100644 index 00000000..b8ca5516 --- /dev/null +++ b/src/verify_report.h @@ -0,0 +1,237 @@ +/* + Copyright (C) 2022 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include "compose.hpp" +#include "file.h" +#include "verify.h" +#include <boost/filesystem.hpp> +#include <vector> + + +namespace dcp { + + +class Formatter +{ +public: + Formatter(boost::filesystem::path file) + : _file(file, "w") + {} + + class Wrap + { + public: + Wrap() = default; + + Wrap(Formatter* formatter, std::string const& close) + : _formatter(formatter) + , _close(close) + {} + + Wrap(Formatter* formatter, std::string const& close, std::function<void ()> closer) + : _formatter(formatter) + , _close(close) + , _closer(closer) + {} + + Wrap(Wrap&& other) + { + std::swap(_formatter, other._formatter); + std::swap(_close, other._close); + std::swap(_closer, other._closer); + } + + ~Wrap() + { + if (_formatter) { + _formatter->file().puts(_close.c_str()); + } + if (_closer) { + _closer(); + } + } + + private: + Formatter* _formatter = nullptr; + std::string _close; + std::function<void ()> _closer = nullptr; + }; + + virtual Wrap document() { return {}; } + + virtual void heading(std::string const& text) = 0; + virtual void subheading(std::string const& text) = 0; + virtual Wrap body() { return {}; } + + virtual Wrap unordered_list() = 0; + virtual void list_item(std::string const& text, boost::optional<std::string> type = {}) = 0; + + virtual std::function<std::string (std::string)> process_string() = 0; + virtual std::function<std::string (std::string)> process_filename() = 0; + + dcp::File& file() { + return _file; + } + +protected: + dcp::File _file; +}; + + +class TextFormatter : public Formatter +{ +public: + TextFormatter(boost::filesystem::path file) + : Formatter(file) + {} + + void heading(std::string const& text) override { + print(text); + } + + void subheading(std::string const& text) override { + print(""); + print(text); + } + + Wrap unordered_list() override { + _indent++; + return Wrap(this, "", [this]() { _indent--; }); + } + + void list_item(std::string const& text, boost::optional<std::string> type = {}) override { + LIBDCP_UNUSED(type); + for (int i = 0; i < _indent * 2; ++i) { + _file.puts(" "); + } + _file.puts("* "); + print(text); + } + + std::function<std::string (std::string)> process_string() override { + return [](std::string s) { + return s; + }; + } + + std::function<std::string (std::string)> process_filename() override { + return [](std::string s) { + return s; + }; + } + +private: + void print(std::string const& text) { + _file.puts(text.c_str()); + _file.puts("\n"); + } + + int _indent = 0; +}; + + +class HTMLFormatter : public Formatter +{ +public: + HTMLFormatter(boost::filesystem::path file) + : Formatter(file) + {} + + void heading(std::string const& text) override { + tagged("h1", text); + } + + void subheading(std::string const& text) override { + tagged("h2", text); + } + + Wrap document() override { + auto html = wrapped("html"); + auto head = wrapped("head"); + auto style = wrapped("style"); + _file.puts("li {\n" + " margin: 2px;\n" + " padding: 2px 2px 2px 1em;\n" + "}\n" + ); + _file.puts("li.ok {\n" + " background-color: #00ff00;\n" + "}\n" + "li.warning {\n" + " background-color: #ffa500;\n" + "}\n" + "li.error {\n" + " background-color: #ff0000;\n" + "}\n" + "ul {\n" + " list-style: none;\n" + "}\n" + ); + return html; + } + + Wrap body() override { + return wrapped("body"); + } + + Wrap unordered_list() override { + return wrapped("ul"); + } + + void list_item(std::string const& text, boost::optional<std::string> type = {}) override { + if (type) { + _file.puts(dcp::String::compose("<li class=\"%1\">%2", *type, text).c_str()); + } else { + _file.puts(dcp::String::compose("<li>%1", text).c_str()); + } + } + + std::function<std::string (std::string)> process_string() override { + return [](std::string s) { + boost::replace_all(s, "<", "<"); + boost::replace_all(s, ">", ">"); + return s; + }; + } + + std::function<std::string (std::string)> process_filename() override { + return [](std::string s) { + return String::compose("<code>%1</code>", s); + }; + } + +private: + void tagged(std::string tag, std::string content) { + _file.puts(String::compose("<%1>%2</%3>\n", tag, content, tag).c_str()); + }; + + Wrap wrapped(std::string const& tag) { + _file.puts(String::compose("<%1>", tag).c_str()); + return Wrap(this, String::compose("</%1>", tag)); + }; +}; + + +extern void verify_report(dcp::VerificationResult const& result, Formatter& formatter); + + +} + diff --git a/src/wscript b/src/wscript index c2d499c8..adaa57bb 100644 --- a/src/wscript +++ b/src/wscript @@ -125,6 +125,7 @@ def build(bld): v_align.cc verify.cc verify_j2k.cc + verify_report.cc version.cc """ @@ -232,6 +233,7 @@ def build(bld): v_align.h verify.h verify_j2k.h + verify_report.h version.h warnings.h """ @@ -244,7 +246,7 @@ def build(bld): obj.name = 'libdcp%s' % bld.env.API_VERSION obj.target = 'dcp%s' % bld.env.API_VERSION obj.export_includes = ['.'] - obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1 ASDCPLIB_CTH XERCES' + obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1 ASDCPLIB_DCPOMATIC XERCES' obj.source = source # Library for gcov @@ -256,7 +258,7 @@ def build(bld): obj.name = 'libdcp%s_gcov' % bld.env.API_VERSION obj.target = 'dcp%s_gcov' % bld.env.API_VERSION obj.export_includes = ['.'] - obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1 ASDCPLIB_CTH XERCES' + obj.uselib = 'BOOST_FILESYSTEM BOOST_SIGNALS2 BOOST_DATETIME OPENSSL SIGC++ LIBXML++ OPENJPEG CXML XMLSEC1 ASDCPLIB_DCPOMATIC XERCES' obj.use = 'libkumu-libdcp%s libasdcp-libdcp%s' % (bld.env.API_VERSION, bld.env.API_VERSION) obj.source = source obj.cppflags = ['-fprofile-arcs', '-ftest-coverage', '-fno-inline', '-fno-default-inline', '-fno-elide-constructors', '-g', '-O0'] |
