diff options
Diffstat (limited to 'src')
98 files changed, 3699 insertions, 1316 deletions
diff --git a/src/asset_factory.cc b/src/asset_factory.cc index be4f6b49..26811366 100644 --- a/src/asset_factory.cc +++ b/src/asset_factory.cc @@ -40,11 +40,12 @@ #include "asset_factory.h" #include "atmos_asset.h" #include "compose.hpp" -#include "mono_picture_asset.h" +#include "mono_j2k_picture_asset.h" +#include "mono_mpeg2_picture_asset.h" #include "smpte_subtitle_asset.h" #include "sound_asset.h" -#include "stereo_picture_asset.h" -#include "stereo_picture_asset.h" +#include "stereo_j2k_picture_asset.h" +#include "stereo_j2k_picture_asset.h" #include <memory> @@ -61,21 +62,23 @@ 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()); } switch (type) { case ASDCP::ESS_UNKNOWN: + throw ReadError("Unknown asset type"); case ASDCP::ESS_MPEG2_VES: - throw ReadError ("MPEG2 video essences are not supported"); + return make_shared<MonoMPEG2PictureAsset>(path); case ASDCP::ESS_JPEG_2000: try { - return make_shared<MonoPictureAsset>(path); + return make_shared<MonoJ2KPictureAsset>(path); } catch (dcp::MXFFileError& e) { if (ignore_incorrect_picture_mxf_type && e.number() == ASDCP::RESULT_SFORMAT) { /* Tried to load it as mono but the error says it's stereo; try that instead */ - auto stereo = make_shared<StereoPictureAsset>(path); + auto stereo = make_shared<StereoJ2KPictureAsset>(path); if (stereo && found_threed_marked_as_twod) { *found_threed_marked_as_twod = true; } @@ -88,7 +91,7 @@ dcp::asset_factory (boost::filesystem::path path, bool ignore_incorrect_picture_ case ASDCP::ESS_PCM_24b_96k: return make_shared<SoundAsset>(path); case ASDCP::ESS_JPEG_2000_S: - return make_shared<StereoPictureAsset>(path); + return make_shared<StereoJ2KPictureAsset>(path); case ASDCP::ESS_TIMED_TEXT: return make_shared<SMPTESubtitleAsset>(path); case ASDCP::ESS_DCDATA_DOLBY_ATMOS: diff --git a/src/asset_list.h b/src/asset_list.h index 5c50495d..29563a8a 100644 --- a/src/asset_list.h +++ b/src/asset_list.h @@ -32,8 +32,8 @@ */ -#ifndef DCP_ASSET_LIST_H -#define DCP_ASSET_LIST_H +#ifndef LIBDCP_ASSET_LIST_H +#define LIBDCP_ASSET_LIST_H #include "types.h" 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..091ac915 100644 --- a/src/asset_reader.h +++ b/src/asset_reader.h @@ -53,9 +53,9 @@ namespace dcp { class AtmosAsset; -class MonoPictureAsset; +class MonoJ2KPictureAsset; class SoundAsset; -class StereoPictureAsset; +class StereoJ2KPictureAsset; template <class R, class F> @@ -90,14 +90,16 @@ protected: private: friend class AtmosAsset; - friend class MonoPictureAsset; + friend class MonoJ2KPictureAsset; + friend class MonoMPEG2PictureAsset; friend class SoundAsset; - friend class StereoPictureAsset; + friend class StereoJ2KPictureAsset; - explicit AssetReader (Asset const * asset, boost::optional<Key> key, Standard standard) + 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/behaviour.h b/src/behaviour.h new file mode 100644 index 00000000..1600966c --- /dev/null +++ b/src/behaviour.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2024 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. +*/ + + +#ifndef LIBDCP_BEHAVIOUR_H +#define LIBDCP_BEHAVIOUR_H + + +namespace dcp { + + +enum class Behaviour { + OVERWRITE_EXISTING, + MAKE_NEW +}; + + +} + + +#endif + 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); diff --git a/src/chromaticity.h b/src/chromaticity.h index 41bb8fda..52edd7bf 100644 --- a/src/chromaticity.h +++ b/src/chromaticity.h @@ -37,8 +37,8 @@ */ -#ifndef DCP_CHROMATICITY_H -#define DCP_CHROMATICITY_H +#ifndef LIBDCP_CHROMATICITY_H +#define LIBDCP_CHROMATICITY_H #include <cmath> diff --git a/src/colour_conversion.h b/src/colour_conversion.h index 8501699a..29140541 100644 --- a/src/colour_conversion.h +++ b/src/colour_conversion.h @@ -37,8 +37,8 @@ */ -#ifndef DCP_COLOUR_CONVERSION_H -#define DCP_COLOUR_CONVERSION_H +#ifndef LIBDCP_COLOUR_CONVERSION_H +#define LIBDCP_COLOUR_CONVERSION_H #include "chromaticity.h" @@ -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 */ @@ -51,15 +51,16 @@ #include "font_asset.h" #include "interop_subtitle_asset.h" #include "metadata.h" -#include "mono_picture_asset.h" -#include "picture_asset.h" +#include "mono_j2k_picture_asset.h" +#include "mono_mpeg2_picture_asset.h" +#include "j2k_picture_asset.h" #include "pkl.h" #include "raw_convert.h" #include "reel_asset.h" #include "reel_subtitle_asset.h" #include "smpte_subtitle_asset.h" #include "sound_asset.h" -#include "stereo_picture_asset.h" +#include "stereo_j2k_picture_asset.h" #include "util.h" #include "verify.h" #include "warnings.h" @@ -246,7 +247,8 @@ DCP::read (vector<dcp::VerificationNote>* notes, bool ignore_incorrect_picture_m other_assets.push_back (make_shared<InteropSubtitleAsset>(path)); } } else if ( - *pkl_type == remove_parameters(PictureAsset::static_pkl_type(standard)) || + *pkl_type == remove_parameters(J2KPictureAsset::static_pkl_type(standard)) || + *pkl_type == remove_parameters(MPEG2PictureAsset::static_pkl_type(standard)) || *pkl_type == remove_parameters(SoundAsset::static_pkl_type(standard)) || *pkl_type == remove_parameters(AtmosAsset::static_pkl_type(standard)) || *pkl_type == remove_parameters(SMPTESubtitleAsset::static_pkl_type(standard)) @@ -441,7 +443,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/dcp_time.h b/src/dcp_time.h index 506dafda..21b59921 100644 --- a/src/dcp_time.h +++ b/src/dcp_time.h @@ -37,8 +37,8 @@ */ -#ifndef LIBDCP_TIME_H -#define LIBDCP_TIME_H +#ifndef LIBDCP_DCP_TIME_H +#define LIBDCP_DCP_TIME_H #include "types.h" 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/exceptions.h b/src/exceptions.h index 8d85f02a..3858b763 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -140,6 +140,33 @@ public: }; +class MPEG2CodecError : public MiscError +{ +public: + explicit MPEG2CodecError(std::string message) + : MiscError(message) + {} +}; + + +class MPEG2DecompressionError : public ReadError +{ +public: + explicit MPEG2DecompressionError(std::string message) + : ReadError(message) + {} +}; + + +class MPEG2CompressionError : public MiscError +{ +public: + explicit MPEG2CompressionError(std::string message) + : MiscError(message) + {} +}; + + class BadContentKindError : public ReadError { public: diff --git a/src/ffmpeg_image.cc b/src/ffmpeg_image.cc new file mode 100644 index 00000000..deebe1b6 --- /dev/null +++ b/src/ffmpeg_image.cc @@ -0,0 +1,122 @@ +/* + Copyright (C) 2024 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 "ffmpeg_image.h" +#include "types.h" +extern "C" { +#include <libavutil/pixfmt.h> +} + + +using namespace dcp; + + +FFmpegImage::FFmpegImage(int64_t pts) +{ + auto const width = size().width; + auto const height = size().height; + + _frame = av_frame_alloc(); + if (!_frame) { + throw std::bad_alloc(); + } + + _frame->buf[0] = av_buffer_alloc(width * height); + _frame->buf[1] = av_buffer_alloc(width * height / 4); + _frame->buf[2] = av_buffer_alloc(width * height / 4); + + _frame->linesize[0] = width; + _frame->linesize[1] = width / 2; + _frame->linesize[2] = width / 2; + + for (auto i = 0; i < 3; ++i) { + _frame->data[i] = _frame->buf[i]->data; + } + + _frame->width = width; + _frame->height = height; + _frame->format = AV_PIX_FMT_YUV420P; + _frame->pts = pts; +} + + +void +FFmpegImage::set_pts(int64_t pts) +{ + _frame->pts = pts; +} + + +uint8_t* +FFmpegImage::y() +{ + return _frame->data[0]; +} + + +int +FFmpegImage::y_stride() const +{ + return _frame->linesize[0]; +} + + +uint8_t* +FFmpegImage::u() +{ + return _frame->data[1]; +} + + +int +FFmpegImage::u_stride() const +{ + return _frame->linesize[1]; +} + + +uint8_t* +FFmpegImage::v() +{ + return _frame->data[2]; +} + + +int +FFmpegImage::v_stride() const +{ + return _frame->linesize[2]; +} + + diff --git a/src/ffmpeg_image.h b/src/ffmpeg_image.h new file mode 100644 index 00000000..44eb0db7 --- /dev/null +++ b/src/ffmpeg_image.h @@ -0,0 +1,107 @@ +/* + Copyright (C) 2023 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. +*/ + + +#ifndef LIBDCP_FFMPEG_IMAGE_H +#define LIBDCP_FFMPEG_IMAGE_H + + +#include "types.h" +#include "warnings.h" +LIBDCP_DISABLE_WARNINGS +extern "C" { +#include <libavutil/frame.h> +} +LIBDCP_ENABLE_WARNINGS +#include <algorithm> +#include <vector> + + +namespace dcp { + + +class FFmpegImage +{ +public: + explicit FFmpegImage(int64_t pts); + + explicit FFmpegImage(AVFrame* frame) + : _frame(frame) + {} + + FFmpegImage(FFmpegImage const& other) = delete; + FFmpegImage& operator=(FFmpegImage const& other) = delete; + + FFmpegImage(FFmpegImage&& other) { + std::swap(_frame, other._frame); + } + + FFmpegImage& operator=(FFmpegImage&& other) { + std::swap(_frame, other._frame); + return *this; + } + + ~FFmpegImage() + { + av_frame_free(&_frame); + } + + AVFrame const * frame() const { + return _frame; + } + + uint8_t* y(); + int y_stride() const; + + uint8_t* u(); + int u_stride() const; + + uint8_t* v(); + int v_stride() const; + + Size size() const { + return { 1920, 1080 }; + } + + void set_pts(int64_t pts); + +private: + AVFrame* _frame = nullptr; +}; + + +} + + +#endif + diff --git a/src/frame_info.h b/src/frame_info.h new file mode 100644 index 00000000..325350c4 --- /dev/null +++ b/src/frame_info.h @@ -0,0 +1,109 @@ +/* + Copyright (C) 2012-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. +*/ + + +#ifndef LIBDCP_FRAME_INFO_H +#define LIBDCP_FRAME_INFO_H + + +#include "warnings.h" +LIBDCP_DISABLE_WARNINGS +#include <asdcp/AS_DCP.h> +LIBDCP_ENABLE_WARNINGS +#include <stdint.h> +#include <string> + + +namespace dcp { + + +/** @class FrameInfo + * @brief Information about a single frame (either a monoscopic frame or a left *or* right eye stereoscopic frame) + */ +struct FrameInfo +{ + FrameInfo () = default; + + FrameInfo(uint64_t o, uint64_t s, std::string h) + : offset(o) + , size(s) + , hash(h) + {} + + uint64_t offset = 0; + uint64_t size = 0; + std::string hash; +}; + + +struct J2KFrameInfo : public FrameInfo +{ + J2KFrameInfo() = default; + + J2KFrameInfo(uint64_t offset_, uint64_t size_, std::string hash_) + : FrameInfo(offset_, size_, hash_) + {} +}; + + +struct MPEG2FrameInfo : public FrameInfo +{ + MPEG2FrameInfo() = default; + + MPEG2FrameInfo( + uint64_t offset_, + uint64_t size_, + std::string hash_, + ASDCP::MPEG2::FrameType_t type_, + bool gop_start_, + bool closed_gop_, + uint8_t temporal_offset_ + ) + : FrameInfo(offset_, size_, hash_) + , type(type_) + , gop_start(gop_start_) + , closed_gop(closed_gop_) + , temporal_offset(temporal_offset_) + {} + + ASDCP::MPEG2::FrameType_t type; + bool gop_start; + bool closed_gop; + uint8_t temporal_offset; +}; + + +} + + +#endif 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/interop_subtitle_asset.h b/src/interop_subtitle_asset.h index f63740d5..ad1c68a7 100644 --- a/src/interop_subtitle_asset.h +++ b/src/interop_subtitle_asset.h @@ -37,8 +37,8 @@ */ -#ifndef DCP_INTEROP_SUBTITLE_ASSET_H -#define DCP_INTEROP_SUBTITLE_ASSET_H +#ifndef LIBDCP_INTEROP_SUBTITLE_ASSET_H +#define LIBDCP_INTEROP_SUBTITLE_ASSET_H #include "subtitle_asset.h" diff --git a/src/j2k_picture_asset.cc b/src/j2k_picture_asset.cc new file mode 100644 index 00000000..98792253 --- /dev/null +++ b/src/j2k_picture_asset.cc @@ -0,0 +1,229 @@ +/* + Copyright (C) 2012-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. +*/ + + +/** @file src/picture_asset.cc + * @brief J2KPictureAsset class + */ + + +#include "compose.hpp" +#include "dcp_assert.h" +#include "equality_options.h" +#include "exceptions.h" +#include "j2k_transcode.h" +#include "openjpeg_image.h" +#include "j2k_picture_asset.h" +#include "j2k_picture_asset_writer.h" +#include "util.h" +#include <asdcp/AS_DCP.h> +#include <asdcp/KM_fileio.h> +#include <libxml++/nodes/element.h> +#include <boost/filesystem.hpp> +#include <list> +#include <stdexcept> + + +using std::string; +using std::list; +using std::vector; +using std::max; +using std::pair; +using std::make_pair; +using std::shared_ptr; +using namespace dcp; + + +J2KPictureAsset::J2KPictureAsset(boost::filesystem::path file) + : PictureAsset(file) +{ + +} + + +J2KPictureAsset::J2KPictureAsset(Fraction edit_rate, Standard standard) + : PictureAsset(edit_rate, standard) +{ + +} + + +void +J2KPictureAsset::read_picture_descriptor (ASDCP::JP2K::PictureDescriptor const & desc) +{ + _size.width = desc.StoredWidth; + _size.height = desc.StoredHeight; + _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator); + _intrinsic_duration = desc.ContainerDuration; + _frame_rate = Fraction (desc.SampleRate.Numerator, desc.SampleRate.Denominator); + _screen_aspect_ratio = Fraction (desc.AspectRatio.Numerator, desc.AspectRatio.Denominator); +} + + +bool +J2KPictureAsset::descriptor_equals ( + ASDCP::JP2K::PictureDescriptor const & a, ASDCP::JP2K::PictureDescriptor const & b, NoteHandler note + ) const +{ + if ( + a.EditRate != b.EditRate || + a.SampleRate != b.SampleRate || + a.StoredWidth != b.StoredWidth || + a.StoredHeight != b.StoredHeight || + a.AspectRatio != b.AspectRatio || + a.Rsize != b.Rsize || + a.Xsize != b.Xsize || + a.Ysize != b.Ysize || + a.XOsize != b.XOsize || + a.YOsize != b.YOsize || + a.XTsize != b.XTsize || + a.YTsize != b.YTsize || + a.XTOsize != b.XTOsize || + a.YTOsize != b.YTOsize || + a.Csize != b.Csize +// a.CodingStyleDefault != b.CodingStyleDefault || +// a.QuantizationDefault != b.QuantizationDefault + ) { + + note (NoteType::ERROR, "video MXF picture descriptors differ"); + return false; + } + + if (a.ContainerDuration != b.ContainerDuration) { + note (NoteType::ERROR, "video container durations differ"); + } + +// for (unsigned int j = 0; j < ASDCP::JP2K::MaxComponents; ++j) { +// if (a.ImageComponents[j] != b.ImageComponents[j]) { +// notes.pack_start ("video MXF picture descriptors differ"); +// } +// } + + return true; +} + + +bool +J2KPictureAsset::frame_buffer_equals ( + int frame, EqualityOptions const& opt, NoteHandler note, + uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B + ) const +{ + if (size_A == size_B && memcmp (data_A, data_B, size_A) == 0) { + note (NoteType::NOTE, "J2K identical"); + /* Easy result; the J2K data is identical */ + return true; + } + + /* Decompress the images to bitmaps */ + auto image_A = decompress_j2k (const_cast<uint8_t*>(data_A), size_A, 0); + auto image_B = decompress_j2k (const_cast<uint8_t*>(data_B), size_B, 0); + + /* Compare them */ + + vector<int> abs_diffs (image_A->size().width * image_A->size().height * 3); + int d = 0; + int max_diff = 0; + + for (int c = 0; c < 3; ++c) { + + if (image_A->size() != image_B->size()) { + note (NoteType::ERROR, String::compose ("image sizes for frame %1 differ", frame)); + return false; + } + + int const pixels = image_A->size().width * image_A->size().height; + for (int j = 0; j < pixels; ++j) { + int const t = abs (image_A->data(c)[j] - image_B->data(c)[j]); + abs_diffs[d++] = t; + max_diff = max (max_diff, t); + } + } + + uint64_t total = 0; + for (vector<int>::iterator j = abs_diffs.begin(); j != abs_diffs.end(); ++j) { + total += *j; + } + + double const mean = double (total) / abs_diffs.size (); + + uint64_t total_squared_deviation = 0; + for (auto j: abs_diffs) { + total_squared_deviation += pow (j - mean, 2); + } + + auto const std_dev = sqrt (double (total_squared_deviation) / abs_diffs.size()); + + note (NoteType::NOTE, String::compose("mean difference %1 deviation %2", mean, std_dev)); + + if (mean > opt.max_mean_pixel_error) { + note ( + NoteType::ERROR, + String::compose ("mean %1 out of range %2 in frame %3", mean, opt.max_mean_pixel_error, frame) + ); + + return false; + } + + if (std_dev > opt.max_std_dev_pixel_error) { + note ( + NoteType::ERROR, + String::compose ("standard deviation %1 out of range %2 in frame %3", std_dev, opt.max_std_dev_pixel_error, frame) + ); + + return false; + } + + return true; +} + + +string +J2KPictureAsset::static_pkl_type (Standard standard) +{ + switch (standard) { + case Standard::INTEROP: + return "application/x-smpte-mxf;asdcpKind=Picture"; + case Standard::SMPTE: + return "application/mxf"; + default: + DCP_ASSERT (false); + } +} + + +string +J2KPictureAsset::pkl_type (Standard standard) const +{ + return static_pkl_type (standard); +} diff --git a/src/j2k_picture_asset.h b/src/j2k_picture_asset.h new file mode 100644 index 00000000..972de43e --- /dev/null +++ b/src/j2k_picture_asset.h @@ -0,0 +1,110 @@ +/* + Copyright (C) 2012-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. +*/ + + +/** @file src/j2k_picture_asset.h + * @brief J2KPictureAsset class + */ + + +#ifndef LIBDCP_J2K_PICTURE_ASSET_H +#define LIBDCP_J2K_PICTURE_ASSET_H + + +#include "behaviour.h" +#include "mxf.h" +#include "metadata.h" +#include "picture_asset.h" +#include "util.h" + + +namespace ASDCP { + namespace JP2K { + struct PictureDescriptor; + } +} + + +namespace dcp { + + +class MonoJ2KPictureFrame; +class StereoJ2KPictureFrame; +class J2KPictureAssetWriter; + + +/** @class J2KPictureAsset + * @brief An asset made up of JPEG2000 data + */ +class J2KPictureAsset : public PictureAsset +{ +public: + /** Load a J2KPictureAsset from a file */ + explicit J2KPictureAsset (boost::filesystem::path file); + + /** Create a new J2KPictureAsset with a given edit rate and standard */ + J2KPictureAsset (Fraction edit_rate, Standard standard); + + virtual std::shared_ptr<J2KPictureAssetWriter> start_write ( + boost::filesystem::path file, + Behaviour behaviour + ) = 0; + + static std::string static_pkl_type (Standard standard); + +protected: + friend class MonoJ2KPictureAssetWriter; + friend class StereoJ2KPictureAssetWriter; + + bool frame_buffer_equals ( + int frame, EqualityOptions const& opt, NoteHandler note, + uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B + ) const; + + bool descriptor_equals ( + ASDCP::JP2K::PictureDescriptor const & a, + ASDCP::JP2K::PictureDescriptor const & b, + NoteHandler note + ) const; + + void read_picture_descriptor (ASDCP::JP2K::PictureDescriptor const &); + +private: + std::string pkl_type (Standard standard) const override; +}; + + +} + + +#endif diff --git a/src/picture_asset_writer.cc b/src/j2k_picture_asset_writer.cc index c30be1bc..75be51ba 100644 --- a/src/picture_asset_writer.cc +++ b/src/j2k_picture_asset_writer.cc @@ -33,13 +33,13 @@ /** @file src/picture_asset_writer.cc - * @brief PictureAssetWriter and FrameInfo classes + * @brief J2KPictureAssetWriter and FrameInfo classes */ -#include "picture_asset_writer.h" +#include "j2k_picture_asset_writer.h" #include "exceptions.h" -#include "picture_asset.h" +#include "j2k_picture_asset.h" #include <asdcp/KM_fileio.h> #include <asdcp/AS_DCP.h> #include <inttypes.h> @@ -51,7 +51,7 @@ using std::shared_ptr; using namespace dcp; -PictureAssetWriter::PictureAssetWriter (PictureAsset* asset, boost::filesystem::path file, bool overwrite) +J2KPictureAssetWriter::J2KPictureAssetWriter (J2KPictureAsset* asset, boost::filesystem::path file, bool overwrite) : AssetWriter (asset, file) , _picture_asset (asset) , _overwrite (overwrite) @@ -60,8 +60,8 @@ PictureAssetWriter::PictureAssetWriter (PictureAsset* asset, boost::filesystem:: } -FrameInfo -PictureAssetWriter::write (Data const& data) +J2KFrameInfo +J2KPictureAssetWriter::write (Data const& data) { return write (data.data(), data.size()); } diff --git a/src/picture_asset_writer.h b/src/j2k_picture_asset_writer.h index 0caa8815..0077b3e0 100644 --- a/src/picture_asset_writer.h +++ b/src/j2k_picture_asset_writer.h @@ -32,16 +32,17 @@ */ -/** @file src/picture_asset_writer.h - * @brief PictureAssetWriter and FrameInfo classes. +/** @file src/j2k_picture_asset_writer.h + * @brief J2KPictureAssetWriter and FrameInfo classes. */ -#ifndef LIBDCP_PICTURE_ASSET_WRITER_H -#define LIBDCP_PICTURE_ASSET_WRITER_H +#ifndef LIBDCP_J2K_PICTURE_ASSET_WRITER_H +#define LIBDCP_J2K_PICTURE_ASSET_WRITER_H #include "asset_writer.h" +#include "frame_info.h" #include "metadata.h" #include <boost/utility.hpp> #include <memory> @@ -53,46 +54,27 @@ namespace dcp { class Data; -class PictureAsset; +class J2KPictureAsset; -/** @class FrameInfo - * @brief Information about a single frame (either a monoscopic frame or a left *or* right eye stereoscopic frame) - */ -struct FrameInfo -{ - FrameInfo () {} - - FrameInfo (uint64_t o, uint64_t s, std::string h) - : offset (o) - , size (s) - , hash (h) - {} - - uint64_t offset = 0; - uint64_t size = 0; - std::string hash; -}; - - -/** @class PictureAssetWriter +/** @class J2KPictureAssetWriter * @brief Parent class for classes which write picture assets. */ -class PictureAssetWriter : public AssetWriter +class J2KPictureAssetWriter : public AssetWriter { public: - virtual FrameInfo write (uint8_t const *, int) = 0; - virtual void fake_write (int) = 0; + virtual J2KFrameInfo write(uint8_t const *, int) = 0; + virtual void fake_write(J2KFrameInfo const& info) = 0; - FrameInfo write (Data const& data); + J2KFrameInfo write(Data const& data); protected: template <class P, class Q> - friend void start (PictureAssetWriter *, std::shared_ptr<P>, Q *, uint8_t const *, int); + friend void start (J2KPictureAssetWriter *, std::shared_ptr<P>, Q *, uint8_t const *, int); - PictureAssetWriter (PictureAsset *, boost::filesystem::path, bool); + J2KPictureAssetWriter (J2KPictureAsset *, boost::filesystem::path, bool); - PictureAsset* _picture_asset = nullptr; + J2KPictureAsset* _picture_asset = nullptr; bool _overwrite = false; }; diff --git a/src/picture_asset_writer_common.cc b/src/j2k_picture_asset_writer_common.cc index 82866aac..a86da194 100644 --- a/src/picture_asset_writer_common.cc +++ b/src/j2k_picture_asset_writer_common.cc @@ -32,8 +32,8 @@ */ -/** @file src/picture_asset_writer_common.cc - * @brief Common parts of PictureAssetWriter +/** @file src/j2k_picture_asset_writer_common.cc + * @brief Common parts of J2KPictureAssetWriter */ @@ -46,9 +46,9 @@ using std::shared_ptr; namespace dcp { -struct ASDCPStateBase +struct ASDCPJ2KStateBase { - ASDCPStateBase () + ASDCPJ2KStateBase() : frame_buffer (4 * Kumu::Megabyte) {} @@ -63,7 +63,7 @@ struct ASDCPStateBase template <class P, class Q> -void dcp::start (PictureAssetWriter* writer, shared_ptr<P> state, Q* asset, uint8_t const * data, int size) +void dcp::start (J2KPictureAssetWriter* writer, shared_ptr<P> state, Q* asset, uint8_t const * data, int size) { asset->set_file (writer->_file); diff --git a/src/language_tag.h b/src/language_tag.h index 6f46c16b..3232dda9 100644 --- a/src/language_tag.h +++ b/src/language_tag.h @@ -32,7 +32,7 @@ */ -/** @file src/language_tag.cc +/** @file src/language_tag.h * @brief LanguageTag class */ diff --git a/src/locale_convert.h b/src/locale_convert.h index 37510a96..1323e704 100644 --- a/src/locale_convert.h +++ b/src/locale_convert.h @@ -32,7 +32,7 @@ */ -/** @file src/locale_convert.cc +/** @file src/locale_convert.h * @brief Methods to convert to/from string using the current locale */ diff --git a/src/mono_picture_asset.cc b/src/mono_j2k_picture_asset.cc index a72fd7d4..f718525d 100644 --- a/src/mono_picture_asset.cc +++ b/src/mono_j2k_picture_asset.cc @@ -33,7 +33,7 @@ /** @file src/mono_picture_asset.cc - * @brief MonoPictureAsset class + * @brief MonoJ2KPictureAsset class */ @@ -42,10 +42,10 @@ #include "equality_options.h" #include "exceptions.h" #include "filesystem.h" -#include "mono_picture_asset.h" -#include "mono_picture_asset_reader.h" -#include "mono_picture_asset_writer.h" -#include "mono_picture_frame.h" +#include "mono_j2k_picture_asset.h" +#include "mono_j2k_picture_asset_reader.h" +#include "mono_j2k_picture_asset_writer.h" +#include "mono_j2k_picture_frame.h" #include <asdcp/AS_DCP.h> #include <asdcp/KM_fileio.h> @@ -63,10 +63,11 @@ using namespace boost::placeholders; using namespace dcp; -MonoPictureAsset::MonoPictureAsset (boost::filesystem::path file) - : PictureAsset (file) +MonoJ2KPictureAsset::MonoJ2KPictureAsset (boost::filesystem::path file) + : J2KPictureAsset (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)); @@ -88,8 +89,8 @@ MonoPictureAsset::MonoPictureAsset (boost::filesystem::path file) } -MonoPictureAsset::MonoPictureAsset (Fraction edit_rate, Standard standard) - : PictureAsset (edit_rate, standard) +MonoJ2KPictureAsset::MonoJ2KPictureAsset (Fraction edit_rate, Standard standard) + : J2KPictureAsset (edit_rate, standard) { } @@ -103,20 +104,21 @@ storing_note_handler (list<pair<NoteType, string>>& notes, NoteType t, string s) bool -MonoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const +MonoJ2KPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const { - if (!dynamic_pointer_cast<const MonoPictureAsset>(other)) { + if (!dynamic_pointer_cast<const MonoJ2KPictureAsset>(other)) { 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)) { @@ -136,7 +138,7 @@ MonoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& o return false; } - auto other_picture = dynamic_pointer_cast<const MonoPictureAsset> (other); + auto other_picture = dynamic_pointer_cast<const MonoJ2KPictureAsset> (other); DCP_ASSERT (other_picture); bool result = true; @@ -184,23 +186,23 @@ MonoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& o } -shared_ptr<PictureAssetWriter> -MonoPictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour) +shared_ptr<J2KPictureAssetWriter> +MonoJ2KPictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour) { - /* Can't use make_shared here as the MonoPictureAssetWriter constructor is private */ - return shared_ptr<MonoPictureAssetWriter>(new MonoPictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING)); + /* Can't use make_shared here as the MonoJ2KPictureAssetWriter constructor is private */ + return shared_ptr<MonoJ2KPictureAssetWriter>(new MonoJ2KPictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING)); } -shared_ptr<MonoPictureAssetReader> -MonoPictureAsset::start_read () const +shared_ptr<MonoJ2KPictureAssetReader> +MonoJ2KPictureAsset::start_read () const { - /* Can't use make_shared here as the MonoPictureAssetReader constructor is private */ - return shared_ptr<MonoPictureAssetReader>(new MonoPictureAssetReader(this, key(), standard())); + /* Can't use make_shared here as the MonoJ2KPictureAssetReader constructor is private */ + return shared_ptr<MonoJ2KPictureAssetReader>(new MonoJ2KPictureAssetReader(this, key(), standard())); } string -MonoPictureAsset::cpl_node_name () const +MonoJ2KPictureAsset::cpl_node_name () const { return "MainPicture"; } diff --git a/src/mono_picture_asset.h b/src/mono_j2k_picture_asset.h index 9658dcf6..d716b8ff 100644 --- a/src/mono_picture_asset.h +++ b/src/mono_j2k_picture_asset.h @@ -32,50 +32,50 @@ */ -/** @file src/mono_picture_asset.cc - * @brief MonoPictureAsset class +/** @file src/mono_j2k_picture_asset.h + * @brief MonoJ2KPictureAsset class */ -#ifndef LIBDCP_MONO_PICTURE_ASSET_H -#define LIBDCP_MONO_PICTURE_ASSET_H +#ifndef LIBDCP_MONO_J2K_PICTURE_ASSET_H +#define LIBDCP_MONO_J2K_PICTURE_ASSET_H -#include "picture_asset.h" -#include "mono_picture_asset_reader.h" +#include "j2k_picture_asset.h" +#include "mono_j2k_picture_asset_reader.h" namespace dcp { -class MonoPictureAssetWriter; +class MonoJ2KPictureAssetWriter; -/** @class MonoPictureAsset +/** @class MonoJ2KPictureAsset * @brief A 2D (monoscopic) picture asset */ -class MonoPictureAsset : public PictureAsset +class MonoJ2KPictureAsset : public J2KPictureAsset { public: - /** Create a MonoPictureAsset by reading a file. + /** Create a MonoJ2KPictureAsset by reading a file. * @param file Asset file to read. */ - explicit MonoPictureAsset (boost::filesystem::path file); + explicit MonoJ2KPictureAsset (boost::filesystem::path file); - /** Create a MonoPictureAsset with a given edit rate. + /** Create a MonoJ2KPictureAsset with a given edit rate. * @param edit_rate Edit rate (i.e. frame rate) in frames per second. * @param standard DCP standard (INTEROP or SMPTE). */ - MonoPictureAsset(Fraction edit_rate, Standard standard); + MonoJ2KPictureAsset(Fraction edit_rate, Standard standard); - /** Start a progressive write to a MonoPictureAsset. + /** Start a progressive write to a MonoJ2KPictureAsset. * @path file File to write to. * @path behaviour OVERWRITE_EXISTING to overwrite and potentially add to an existing file * (after a write previously failed), MAKE_NEW to create a new file. * If in doubt, use MAKE_NEW here. */ - std::shared_ptr<PictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override; - std::shared_ptr<MonoPictureAssetReader> start_read () const; + std::shared_ptr<J2KPictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override; + std::shared_ptr<MonoJ2KPictureAssetReader> start_read () const; bool equals ( std::shared_ptr<const Asset> other, diff --git a/src/stereo_picture_asset_reader.h b/src/mono_j2k_picture_asset_reader.h index 9cb05263..8376e939 100644 --- a/src/stereo_picture_asset_reader.h +++ b/src/mono_j2k_picture_asset_reader.h @@ -32,23 +32,23 @@ */ -/** @file src/stereo_picture_asset_reader.h - * @brief StereoPictureAssetReader typedef +/** @file src/mono_j2k_picture_asset_reader.h + * @brief MonoJ2KPictureAssetReader typedef */ -#ifndef LIBDCP_STEREO_PICTURE_ASSET_READER_H -#define LIBDCP_STEREO_PICTURE_ASSET_READER_H +#ifndef LIBDCP_MONO_J2K_PICTURE_ASSET_READER_H +#define LIBDCP_MONO_J2K_PICTURE_ASSET_READER_H #include "asset_reader.h" -#include "stereo_picture_frame.h" +#include "mono_j2k_picture_frame.h" namespace dcp { -typedef AssetReader<ASDCP::JP2K::MXFSReader, StereoPictureFrame> StereoPictureAssetReader; +typedef AssetReader<ASDCP::JP2K::MXFReader, MonoJ2KPictureFrame> MonoJ2KPictureAssetReader; } diff --git a/src/mono_picture_asset_writer.cc b/src/mono_j2k_picture_asset_writer.cc index 7fd58114..1188701e 100644 --- a/src/mono_picture_asset_writer.cc +++ b/src/mono_j2k_picture_asset_writer.cc @@ -33,15 +33,15 @@ /** @file src/mono_picture_asset_writer.cc - * @brief MonoPictureAssetWriter class + * @brief MonoJ2KPictureAssetWriter class */ #include "crypto_context.h" #include "dcp_assert.h" #include "exceptions.h" -#include "mono_picture_asset_writer.h" -#include "picture_asset.h" +#include "mono_j2k_picture_asset_writer.h" +#include "j2k_picture_asset.h" #include "warnings.h" LIBDCP_DISABLE_WARNINGS #include <asdcp/AS_DCP.h> @@ -49,7 +49,7 @@ LIBDCP_DISABLE_WARNINGS LIBDCP_ENABLE_WARNINGS -#include "picture_asset_writer_common.cc" +#include "j2k_picture_asset_writer_common.cc" using std::string; @@ -57,7 +57,7 @@ using std::shared_ptr; using namespace dcp; -struct MonoPictureAssetWriter::ASDCPState : public ASDCPStateBase +struct MonoJ2KPictureAssetWriter::ASDCPState : public ASDCPJ2KStateBase { ASDCP::JP2K::MXFWriter mxf_writer; }; @@ -66,15 +66,15 @@ struct MonoPictureAssetWriter::ASDCPState : public ASDCPStateBase /** @param a Asset to write to. `a' must not be deleted while * this writer class still exists, or bad things will happen. */ -MonoPictureAssetWriter::MonoPictureAssetWriter (PictureAsset* asset, boost::filesystem::path file, bool overwrite) - : PictureAssetWriter (asset, file, overwrite) - , _state (new MonoPictureAssetWriter::ASDCPState) +MonoJ2KPictureAssetWriter::MonoJ2KPictureAssetWriter (J2KPictureAsset* asset, boost::filesystem::path file, bool overwrite) + : J2KPictureAssetWriter (asset, file, overwrite) + , _state (new MonoJ2KPictureAssetWriter::ASDCPState) { } -MonoPictureAssetWriter::~MonoPictureAssetWriter() +MonoJ2KPictureAssetWriter::~MonoJ2KPictureAssetWriter() { try { /* Last-resort finalization to close the file, at least */ @@ -86,15 +86,15 @@ MonoPictureAssetWriter::~MonoPictureAssetWriter() void -MonoPictureAssetWriter::start (uint8_t const * data, int size) +MonoJ2KPictureAssetWriter::start (uint8_t const * data, int size) { dcp::start (this, _state, _picture_asset, data, size); _picture_asset->set_frame_rate (_picture_asset->edit_rate()); } -FrameInfo -MonoPictureAssetWriter::write (uint8_t const * data, int size) +J2KFrameInfo +MonoJ2KPictureAssetWriter::write (uint8_t const * data, int size) { DCP_ASSERT (!_finalized); @@ -117,17 +117,17 @@ MonoPictureAssetWriter::write (uint8_t const * data, int size) } ++_frames_written; - return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash); + return J2KFrameInfo(before_offset, _state->mxf_writer.Tell() - before_offset, hash); } void -MonoPictureAssetWriter::fake_write (int size) +MonoJ2KPictureAssetWriter::fake_write(J2KFrameInfo const& info) { DCP_ASSERT (_started); DCP_ASSERT (!_finalized); - auto r = _state->mxf_writer.FakeWriteFrame (size); + auto r = _state->mxf_writer.FakeWriteFrame(info.size); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("error in writing video MXF", _file.string(), r)); } @@ -137,7 +137,7 @@ MonoPictureAssetWriter::fake_write (int size) bool -MonoPictureAssetWriter::finalize () +MonoJ2KPictureAssetWriter::finalize () { if (_started) { auto r = _state->mxf_writer.Finalize(); @@ -147,5 +147,5 @@ MonoPictureAssetWriter::finalize () } _picture_asset->_intrinsic_duration = _frames_written; - return PictureAssetWriter::finalize (); + return J2KPictureAssetWriter::finalize (); } diff --git a/src/mono_picture_asset_writer.h b/src/mono_j2k_picture_asset_writer.h index 551d8c80..b3f57191 100644 --- a/src/mono_picture_asset_writer.h +++ b/src/mono_j2k_picture_asset_writer.h @@ -32,16 +32,16 @@ */ -/** @file src/mono_picture_asset_writer.h - * @brief MonoPictureAssetWriter class +/** @file src/mono_j2k_picture_asset_writer.h + * @brief MonoJ2KPictureAssetWriter class */ -#ifndef LIBDCP_MONO_PICTURE_ASSET_WRITER_H -#define LIBDCP_MONO_PICTURE_ASSET_WRITER_H +#ifndef LIBDCP_MONO_J2K_PICTURE_ASSET_WRITER_H +#define LIBDCP_MONO_J2K_PICTURE_ASSET_WRITER_H -#include "picture_asset_writer.h" +#include "j2k_picture_asset_writer.h" #include <memory> #include <boost/utility.hpp> #include <stdint.h> @@ -51,29 +51,29 @@ namespace dcp { -/** @class MonoPictureAssetWriter - * @brief A helper class for writing to MonoPictureAssets +/** @class MonoJ2KPictureAssetWriter + * @brief A helper class for writing to MonoJ2KPictureAssets * - * Objects of this class can only be created with MonoPictureAsset::start_write(). + * Objects of this class can only be created with MonoJ2KPictureAsset::start_write(). * - * Frames can be written to the MonoPictureAsset by calling write() with a JPEG2000 image + * Frames can be written to the MonoJ2KPictureAsset by calling write() with a JPEG2000 image * (a verbatim .j2c file). finalize() should be called after the last frame has been written, * but if it is not, it will be called by the destructor (though in that case any error * during finalization will be ignored). */ -class MonoPictureAssetWriter : public PictureAssetWriter +class MonoJ2KPictureAssetWriter : public J2KPictureAssetWriter { public: - ~MonoPictureAssetWriter(); + ~MonoJ2KPictureAssetWriter(); - FrameInfo write (uint8_t const *, int) override; - void fake_write (int size) override; + J2KFrameInfo write(uint8_t const *, int) override; + void fake_write(J2KFrameInfo const& info) override; bool finalize () override; private: - friend class MonoPictureAsset; + friend class MonoJ2KPictureAsset; - MonoPictureAssetWriter (PictureAsset* a, boost::filesystem::path file, bool); + MonoJ2KPictureAssetWriter (J2KPictureAsset* a, boost::filesystem::path file, bool); void start (uint8_t const *, int); diff --git a/src/mono_picture_frame.cc b/src/mono_j2k_picture_frame.cc index 2abd57e4..69ba9a59 100644 --- a/src/mono_picture_frame.cc +++ b/src/mono_j2k_picture_frame.cc @@ -33,7 +33,7 @@ /** @file src/mono_picture_frame.cc - * @brief MonoPictureFrame class + * @brief MonoJ2KPictureFrame class */ @@ -44,7 +44,7 @@ #include "file.h" #include "filesystem.h" #include "j2k_transcode.h" -#include "mono_picture_frame.h" +#include "mono_j2k_picture_frame.h" #include "rgb_xyz.h" #include "util.h" #include <asdcp/KM_fileio.h> @@ -58,7 +58,7 @@ using boost::optional; using namespace dcp; -MonoPictureFrame::MonoPictureFrame (boost::filesystem::path path) +MonoJ2KPictureFrame::MonoJ2KPictureFrame (boost::filesystem::path path) { auto const size = filesystem::file_size(path); _buffer.reset(new ASDCP::JP2K::FrameBuffer(size)); @@ -81,7 +81,7 @@ MonoPictureFrame::MonoPictureFrame (boost::filesystem::path path) * @param c Context for decryption, or 0. * @param check_hmac true to check the HMAC and give an error if it is not as expected. */ -MonoPictureFrame::MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, shared_ptr<DecryptionContext> c, bool check_hmac) +MonoJ2KPictureFrame::MonoJ2KPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, shared_ptr<DecryptionContext> c, bool check_hmac) { /* XXX: unfortunate guesswork on this buffer size */ _buffer = make_shared<ASDCP::JP2K::FrameBuffer>(4 * Kumu::Megabyte); @@ -94,7 +94,7 @@ MonoPictureFrame::MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, share } -MonoPictureFrame::MonoPictureFrame (uint8_t const * data, int size) +MonoJ2KPictureFrame::MonoJ2KPictureFrame (uint8_t const * data, int size) { _buffer = make_shared<ASDCP::JP2K::FrameBuffer>(size); _buffer->Size (size); @@ -103,28 +103,28 @@ MonoPictureFrame::MonoPictureFrame (uint8_t const * data, int size) uint8_t const * -MonoPictureFrame::data () const +MonoJ2KPictureFrame::data () const { return _buffer->RoData (); } uint8_t * -MonoPictureFrame::data () +MonoJ2KPictureFrame::data () { return _buffer->Data (); } int -MonoPictureFrame::size () const +MonoJ2KPictureFrame::size () const { return _buffer->Size (); } shared_ptr<OpenJPEGImage> -MonoPictureFrame::xyz_image (int reduce) const +MonoJ2KPictureFrame::xyz_image (int reduce) const { return decompress_j2k (const_cast<uint8_t*>(_buffer->RoData()), _buffer->Size(), reduce); } diff --git a/src/mono_picture_frame.h b/src/mono_j2k_picture_frame.h index 43575864..f08e7343 100644 --- a/src/mono_picture_frame.h +++ b/src/mono_j2k_picture_frame.h @@ -32,13 +32,13 @@ */ -/** @file src/mono_picture_frame.h - * @brief MonoPictureFrame class +/** @file src/mono_j2k_picture_frame.h + * @brief MonoJ2KPictureFrame class */ -#ifndef LIBDCP_MONO_PICTURE_FRAME_H -#define LIBDCP_MONO_PICTURE_FRAME_H +#ifndef LIBDCP_MONO_J2K_PICTURE_FRAME_H +#define LIBDCP_MONO_J2K_PICTURE_FRAME_H #include "asset_reader.h" @@ -64,20 +64,20 @@ namespace dcp { class OpenJPEGImage; -/** @class MonoPictureFrame +/** @class MonoJ2KPictureFrame * @brief A single frame of a 2D (monoscopic) picture asset */ -class MonoPictureFrame : public Data +class MonoJ2KPictureFrame : public Data { public: /** Make a picture frame from a JPEG2000 file. * @param path Path to JPEG2000 file. */ - explicit MonoPictureFrame (boost::filesystem::path path); - MonoPictureFrame (uint8_t const * data, int size); + explicit MonoJ2KPictureFrame (boost::filesystem::path path); + MonoJ2KPictureFrame (uint8_t const * data, int size); - MonoPictureFrame (MonoPictureFrame const&) = delete; - MonoPictureFrame& operator= (MonoPictureFrame const&) = delete; + MonoJ2KPictureFrame (MonoJ2KPictureFrame const&) = delete; + MonoJ2KPictureFrame& operator= (MonoJ2KPictureFrame const&) = delete; /** @param reduce a factor by which to reduce the resolution * of the image, expressed as a power of two (pass 0 for no @@ -95,12 +95,12 @@ public: int size () const override; private: - /* XXX: this is a bit of a shame, but I tried friend MonoPictureAssetReader and it's + /* XXX: this is a bit of a shame, but I tried friend MonoJ2KPictureAssetReader and it's rejected by some (seemingly older) GCCs. */ - friend class AssetReader<ASDCP::JP2K::MXFReader, MonoPictureFrame>; + friend class AssetReader<ASDCP::JP2K::MXFReader, MonoJ2KPictureFrame>; - MonoPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac); + MonoJ2KPictureFrame (ASDCP::JP2K::MXFReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac); std::shared_ptr<ASDCP::JP2K::FrameBuffer> _buffer; }; diff --git a/src/mono_mpeg2_picture_asset.cc b/src/mono_mpeg2_picture_asset.cc new file mode 100644 index 00000000..380da0fe --- /dev/null +++ b/src/mono_mpeg2_picture_asset.cc @@ -0,0 +1,86 @@ +/* + Copyright (C) 2023 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 "filesystem.h" +#include "mono_mpeg2_picture_asset.h" +#include "mono_mpeg2_picture_asset_reader.h" +#include "mono_mpeg2_picture_asset_writer.h" +#include <asdcp/AS_DCP.h> + + +using std::shared_ptr; +using namespace dcp; + + +MonoMPEG2PictureAsset::MonoMPEG2PictureAsset(boost::filesystem::path file) + : MPEG2PictureAsset(file) +{ + Kumu::FileReaderFactory factory; + ASDCP::MPEG2::MXFReader reader(factory); + auto const result = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); + if (ASDCP_FAILURE(result)) { + boost::throw_exception(MXFFileError("could not open MXF file for reading", file.string(), result)); + } + + ASDCP::MPEG2::VideoDescriptor desc; + if (ASDCP_FAILURE(reader.FillVideoDescriptor(desc))) { + boost::throw_exception(ReadError("could not read video MXF information")); + } + + read_video_descriptor(desc); + + ASDCP::WriterInfo info; + if (ASDCP_FAILURE(reader.FillWriterInfo(info))) { + boost::throw_exception(ReadError("could not read video MXF information")); + } + + _id = read_writer_info(info); +} + + +shared_ptr<MonoMPEG2PictureAssetReader> +MonoMPEG2PictureAsset::start_read () const +{ + /* Can't use make_shared here as the MonoMPEG2PictureAssetReader constructor is private */ + return shared_ptr<MonoMPEG2PictureAssetReader>(new MonoMPEG2PictureAssetReader(this, key(), standard())); + +} + + +shared_ptr<MPEG2PictureAssetWriter> +MonoMPEG2PictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour) +{ + /* Can't use make_shared here as the MonoJ2KPictureAssetWriter constructor is private */ + return shared_ptr<MonoMPEG2PictureAssetWriter>(new MonoMPEG2PictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING)); +} diff --git a/src/mono_mpeg2_picture_asset.h b/src/mono_mpeg2_picture_asset.h new file mode 100644 index 00000000..8ef3653e --- /dev/null +++ b/src/mono_mpeg2_picture_asset.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2023 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. +*/ + + +#ifndef LIBDCP_MONO_MPEG2_PICTURE_ASSET_H +#define LIBDCP_MONO_MPEG2_PICTURE_ASSET_H + + +/** @file src/mono_mpeg2_picture_asset.h + * @brief MonoMPEG2PictureAsset class + */ + + +#include "behaviour.h" +#include "mpeg2_picture_asset.h" +#include "mono_mpeg2_picture_asset_reader.h" + + +namespace dcp { + + +class MonoMPEG2PictureAssetWriter; + + +class MonoMPEG2PictureAsset : public MPEG2PictureAsset +{ +public: + MonoMPEG2PictureAsset(Fraction edit_rate) + : MPEG2PictureAsset(edit_rate) + {} + + explicit MonoMPEG2PictureAsset(boost::filesystem::path file); + + std::shared_ptr<MPEG2PictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override; + std::shared_ptr<MonoMPEG2PictureAssetReader> start_read() const; +}; + + + +} + + +#endif diff --git a/src/mono_mpeg2_picture_asset_reader.h b/src/mono_mpeg2_picture_asset_reader.h new file mode 100644 index 00000000..75155f40 --- /dev/null +++ b/src/mono_mpeg2_picture_asset_reader.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2023 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. +*/ + + +/** @file src/mono_mpeg2_picture_asset_reader.h + * @brief MonoJ2KPictureAssetReader typedef + */ + + +#ifndef LIBDCP_MONO_MPEG2_PICTURE_ASSET_READER_H +#define LIBDCP_MONO_MPEG2_PICTURE_ASSET_READER_H + + +#include "asset_reader.h" +#include "mono_mpeg2_picture_frame.h" + + +namespace dcp { + + +typedef AssetReader<ASDCP::MPEG2::MXFReader, MonoMPEG2PictureFrame> MonoMPEG2PictureAssetReader; + + +} + + +#endif + diff --git a/src/mono_mpeg2_picture_asset_writer.cc b/src/mono_mpeg2_picture_asset_writer.cc new file mode 100644 index 00000000..f8101c54 --- /dev/null +++ b/src/mono_mpeg2_picture_asset_writer.cc @@ -0,0 +1,144 @@ +/* + Copyright (C) 2024 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 "mono_mpeg2_picture_asset_writer.h" +#include "mpeg2_picture_asset.h" +#include "mpeg2_picture_asset_writer.h" + + +using std::string; +using namespace dcp; + + +struct MonoMPEG2PictureAssetWriter::ASDCPState : public ASDCPMPEG2StateBase +{ + ASDCP::MPEG2::MXFWriter mxf_writer; +}; + + + + +MonoMPEG2PictureAssetWriter::MonoMPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite) + : MPEG2PictureAssetWriter(asset, file, overwrite) + , _state(new MonoMPEG2PictureAssetWriter::ASDCPState) +{ + asset->set_file(file); +} + + +MonoMPEG2PictureAssetWriter::~MonoMPEG2PictureAssetWriter() +{ + try { + /* Last-resort finalization to close the file, at least */ + if (!_finalized) { + _state->mxf_writer.Finalize(); + } + } catch (...) {} +} + + +void +MonoMPEG2PictureAssetWriter::start(uint8_t const * data, int size) +{ + dcp::start(this, _state, _picture_asset, data, size); + _picture_asset->set_frame_rate (_picture_asset->edit_rate()); +} + + +MPEG2FrameInfo +MonoMPEG2PictureAssetWriter::write(uint8_t const * data, int size) +{ + DCP_ASSERT(!_finalized); + + if (!_started) { + start(data, size); + } + + ASDCP::MPEG2::FrameBuffer buffer; + buffer.SetData(const_cast<uint8_t*>(data), size); + buffer.Size(size); + buffer.PlaintextOffset(0); + + auto const before_offset = _state->mxf_writer.Tell(); + + string hash; + auto const r = _state->mxf_writer.WriteFrame(buffer, _crypto_context->context(), _crypto_context->hmac(), &hash); + if (ASDCP_FAILURE(r)) { + boost::throw_exception(MXFFileError("error in writing video MXF", _file.string(), r)); + } + + ++_frames_written; + return MPEG2FrameInfo( + before_offset, + _state->mxf_writer.Tell() - before_offset, + hash, + buffer.FrameType(), + buffer.GOPStart(), + buffer.ClosedGOP(), + buffer.TemporalOffset() + ); +} + + +void +MonoMPEG2PictureAssetWriter::fake_write(MPEG2FrameInfo const& info) +{ + DCP_ASSERT(_started); + DCP_ASSERT(!_finalized); + + DCP_ASSERT(false); + + auto r = _state->mxf_writer.FakeWriteFrame(info.size, info.type, info.gop_start, info.closed_gop, info.temporal_offset); + if (ASDCP_FAILURE(r)) { + boost::throw_exception(MXFFileError("error in writing video MXF", _file.string(), r)); + } + + ++_frames_written; +} + + +bool +MonoMPEG2PictureAssetWriter::finalize() +{ + if (_started) { + auto r = _state->mxf_writer.Finalize(); + if (ASDCP_FAILURE(r)) { + boost::throw_exception(MXFFileError("error in finalizing video MXF", _file.string(), r)); + } + } + + _picture_asset->_intrinsic_duration = _frames_written; + return MPEG2PictureAssetWriter::finalize(); +} + diff --git a/src/mono_mpeg2_picture_asset_writer.h b/src/mono_mpeg2_picture_asset_writer.h new file mode 100644 index 00000000..bf7a6cc9 --- /dev/null +++ b/src/mono_mpeg2_picture_asset_writer.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2024 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 "mpeg2_picture_asset_writer.h" + + +#include "mpeg2_picture_asset_writer_common.cc" + + +namespace dcp { + + +class MonoMPEG2PictureAsset; + + +class MonoMPEG2PictureAssetWriter : public MPEG2PictureAssetWriter +{ +public: + ~MonoMPEG2PictureAssetWriter(); + + MPEG2FrameInfo write(uint8_t const *, int) override; + void fake_write(MPEG2FrameInfo const& info) override; + bool finalize() override; + +private: + friend class MonoMPEG2PictureAsset; + + MonoMPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite); + + void start(uint8_t const *, int); + + struct ASDCPState; + std::shared_ptr<ASDCPState> _state; +}; + + +} diff --git a/src/mono_mpeg2_picture_frame.cc b/src/mono_mpeg2_picture_frame.cc new file mode 100644 index 00000000..3c79a94c --- /dev/null +++ b/src/mono_mpeg2_picture_frame.cc @@ -0,0 +1,91 @@ +/* + Copyright (C) 2023 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 "mono_mpeg2_picture_frame.h" + + +using std::make_shared; +using std::shared_ptr; +using namespace dcp; + + + +MonoMPEG2PictureFrame::MonoMPEG2PictureFrame(uint8_t const* data, int size) +{ + _buffer = make_shared<ASDCP::MPEG2::FrameBuffer>(size); + memcpy(_buffer->Data(), data, size); + _buffer->Size(size); +} + + +/** Make a picture frame from a 2D (monoscopic) asset. + * @param reader Reader for the asset's MXF file. + * @param n Frame within the asset, not taking EntryPoint into account. + * @param c Context for decryption, or 0. + * @param check_hmac true to check the HMAC and give an error if it is not as expected. + */ +MonoMPEG2PictureFrame::MonoMPEG2PictureFrame(ASDCP::MPEG2::MXFReader* reader, int n, shared_ptr<DecryptionContext> context, bool check_hmac) +{ + /* XXX: unfortunate guesswork on this buffer size */ + _buffer = make_shared<ASDCP::MPEG2::FrameBuffer>(4 * Kumu::Megabyte); + + auto const r = reader->ReadFrame(n, *_buffer, context->context(), check_hmac ? context->hmac() : nullptr); + + if (ASDCP_FAILURE(r)) { + boost::throw_exception(ReadError(String::compose("could not read video frame %1 (%2)", n, static_cast<int>(r)))); + } +} + + +uint8_t const * +MonoMPEG2PictureFrame::data() const +{ + return _buffer->RoData(); +} + + +uint8_t * +MonoMPEG2PictureFrame::data() +{ + return _buffer->Data(); +} + + +int +MonoMPEG2PictureFrame::size() const +{ + return _buffer->Size(); +} + diff --git a/src/mono_mpeg2_picture_frame.h b/src/mono_mpeg2_picture_frame.h new file mode 100644 index 00000000..6a7669f7 --- /dev/null +++ b/src/mono_mpeg2_picture_frame.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2012-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. +*/ + + +#ifndef LIBDCP_MONO_MPEG2_PICTURE_FRAME_H +#define LIBDCP_MONO_MPEG2_PICTURE_FRAME_H + + +#include "asset_reader.h" +#include "data.h" + + +namespace dcp { + + +class MonoMPEG2PictureFrame : public Data +{ +public: + MonoMPEG2PictureFrame(uint8_t const * data, int size); + + MonoMPEG2PictureFrame(MonoMPEG2PictureFrame const&) = delete; + MonoMPEG2PictureFrame& operator=(MonoMPEG2PictureFrame const&) = delete; + + /* XXX: couldn't we just return the frame buffer */ + + /** @return Pointer to MPEG2 data */ + uint8_t const * data() const override; + + /** @return Pointer to MPEG2 data */ + uint8_t* data () override; + + /** @return Size of MPEG2 data in bytes */ + int size() const override; + +private: + /* XXX: this is a bit of a shame, but I tried friend MonoMPEG2PictureAssetReader and it's + rejected by some (seemingly older) GCCs. + */ + friend class AssetReader<ASDCP::MPEG2::MXFReader, MonoMPEG2PictureFrame>; + + MonoMPEG2PictureFrame(ASDCP::MPEG2::MXFReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac); + + /* XXX why is this a shared_ptr? */ + std::shared_ptr<ASDCP::MPEG2::FrameBuffer> _buffer; +}; + + +} + + +#endif diff --git a/src/mpeg2_picture_asset.cc b/src/mpeg2_picture_asset.cc new file mode 100644 index 00000000..6cd0b428 --- /dev/null +++ b/src/mpeg2_picture_asset.cc @@ -0,0 +1,74 @@ +/* + Copyright (C) 2023 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 "mpeg2_picture_asset.h" + + +using std::string; +using namespace dcp; + + +MPEG2PictureAsset::MPEG2PictureAsset(boost::filesystem::path file) + : PictureAsset(file) +{ + +} + + +void +MPEG2PictureAsset::read_video_descriptor(ASDCP::MPEG2::VideoDescriptor const& descriptor) +{ + _size.width = descriptor.StoredWidth; + _size.height = descriptor.StoredHeight; + _edit_rate = Fraction(descriptor.EditRate.Numerator, descriptor.EditRate.Denominator); + _intrinsic_duration = descriptor.ContainerDuration; + _frame_rate = Fraction(descriptor.SampleRate.Numerator, descriptor.SampleRate.Denominator); + _screen_aspect_ratio = Fraction(descriptor.AspectRatio.Numerator, descriptor.AspectRatio.Denominator); +} + + +string +MPEG2PictureAsset::pkl_type (Standard standard) const +{ + DCP_ASSERT(standard == Standard::INTEROP); + return "application/x-smpte-mxf;asdcpKind=Picture"; +} + + +string +MPEG2PictureAsset::static_pkl_type(Standard standard) +{ + DCP_ASSERT(standard == Standard::INTEROP); + return "application/x-smpte-mxf;asdcpKind=Picture"; +} diff --git a/src/mpeg2_picture_asset.h b/src/mpeg2_picture_asset.h new file mode 100644 index 00000000..38b8e0a0 --- /dev/null +++ b/src/mpeg2_picture_asset.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2023 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. +*/ + + +#ifndef LIBDCP_MPEG2_PICTURE_ASSET_H +#define LIBDCP_MPEG2_PICTURE_ASSET_H + + +/** @file src/mpeg2_picture_asset.h + * @brief MPEG2PictureAsset class + */ + + +#include "behaviour.h" +#include "mpeg2_picture_asset_writer.h" +#include "picture_asset.h" +#include <boost/filesystem/path.hpp> + + +namespace ASDCP { + namespace MPEG2 { + struct VideoDescriptor; + } +} + + +namespace dcp { + + +class MPEG2PictureAsset : public PictureAsset +{ +public: + MPEG2PictureAsset(Fraction edit_rate) + : PictureAsset(edit_rate, Standard::INTEROP) + {} + + explicit MPEG2PictureAsset(boost::filesystem::path file); + + virtual std::shared_ptr<MPEG2PictureAssetWriter> start_write( + boost::filesystem::path file, + Behaviour behaviour + ) = 0; + + static std::string static_pkl_type(Standard standard); + +protected: + friend class MonoMPEG2PictureAssetWriter; + + void read_video_descriptor(ASDCP::MPEG2::VideoDescriptor const& descriptor); + +private: + std::string pkl_type(Standard standard) const override; +}; + + +} + + +#endif + diff --git a/src/mpeg2_picture_asset_writer.cc b/src/mpeg2_picture_asset_writer.cc new file mode 100644 index 00000000..f5ead775 --- /dev/null +++ b/src/mpeg2_picture_asset_writer.cc @@ -0,0 +1,48 @@ +/* + Copyright (C) 2024 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 "mpeg2_picture_asset.h" +#include "mpeg2_picture_asset_writer.h" + + +using namespace dcp; + + +MPEG2PictureAssetWriter::MPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite) + : AssetWriter(asset, file) + , _picture_asset(asset) + , _overwrite(overwrite) +{ + asset->set_file(file); +} diff --git a/src/mpeg2_picture_asset_writer.h b/src/mpeg2_picture_asset_writer.h new file mode 100644 index 00000000..a370ef9b --- /dev/null +++ b/src/mpeg2_picture_asset_writer.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2024 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. +*/ + + +#ifndef LIBDCP_MPEG2_PICTURE_ASSET_WRITER_H +#define LIBDCP_MPEG2_PICTURE_ASSET_WRITER_H + + +#include "asset_writer.h" +#include "frame_info.h" + + +namespace dcp { + + +class Data; +class MPEG2PictureAsset; + + +class MPEG2PictureAssetWriter : public AssetWriter +{ +public: + virtual MPEG2FrameInfo write(uint8_t const* data, int size) = 0; + virtual void fake_write(MPEG2FrameInfo const& info) = 0; + + MPEG2FrameInfo write(Data const& data); + +protected: + template <class P, class Q> + friend void start(MPEG2PictureAssetWriter *, std::shared_ptr<P>, Q *, uint8_t const *, int); + + MPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite); + + MPEG2PictureAsset* _picture_asset = nullptr; + bool _overwrite = false; +}; + + +} + + +#endif + diff --git a/src/mpeg2_picture_asset_writer_common.cc b/src/mpeg2_picture_asset_writer_common.cc new file mode 100644 index 00000000..d46057f7 --- /dev/null +++ b/src/mpeg2_picture_asset_writer_common.cc @@ -0,0 +1,93 @@ +/* + Copyright (C) 2012-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. +*/ + + +/** @file src/mpeg2_picture_asset_writer_common.cc + * @brief Common parts of MPEG2PictureAssetWriter + */ + + +#include "filesystem.h" + + +using std::shared_ptr; + + +namespace dcp { + + +class MPEG2PictureAssetWriter; + + +struct ASDCPMPEG2StateBase +{ + ASDCP::MPEG2::Parser mpeg2_parser; + ASDCP::WriterInfo writer_info; + ASDCP::MPEG2::VideoDescriptor video_descriptor; +}; + + +} + + +template <class P, class Q> +void dcp::start(MPEG2PictureAssetWriter* writer, shared_ptr<P> state, Q* asset, uint8_t const * data, int size) +{ + asset->set_file (writer->_file); + + if (ASDCP_FAILURE(state->mpeg2_parser.OpenRead(data, size))) { + boost::throw_exception(MiscError("could not parse MPEG2 frame")); + } + + state->mpeg2_parser.FillVideoDescriptor(state->video_descriptor); + state->video_descriptor.EditRate = ASDCP::Rational(asset->edit_rate().numerator, asset->edit_rate().denominator); + + asset->set_size(Size(state->video_descriptor.StoredWidth, state->video_descriptor.StoredHeight)); + asset->set_screen_aspect_ratio(Fraction(state->video_descriptor.AspectRatio.Numerator, state->video_descriptor.AspectRatio.Denominator)); + + asset->fill_writer_info(&state->writer_info, asset->id()); + + auto r = state->mxf_writer.OpenWrite( + dcp::filesystem::fix_long_path(*asset->file()).string().c_str(), + state->writer_info, + state->video_descriptor, + 16384, + writer->_overwrite + ); + + if (ASDCP_FAILURE(r)) { + boost::throw_exception(MXFFileError("could not open MXF file for writing", asset->file()->string(), r)); + } + + writer->_started = true; +} diff --git a/src/mpeg2_transcode.cc b/src/mpeg2_transcode.cc new file mode 100644 index 00000000..0ac2c1af --- /dev/null +++ b/src/mpeg2_transcode.cc @@ -0,0 +1,216 @@ +/* + Copyright (C) 2023 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 "exceptions.h" +#include "mono_mpeg2_picture_frame.h" +#include "mpeg2_transcode.h" +#include "scope_guard.h" +extern "C" { +#include <libavcodec/avcodec.h> +} + + +using std::make_shared; +using std::shared_ptr; +using std::vector; +using boost::optional; +using namespace dcp; + + +MPEG2Codec::~MPEG2Codec() +{ + avcodec_free_context(&_context); +} + + + +MPEG2Decompressor::MPEG2Decompressor() +{ + _codec = avcodec_find_decoder_by_name("mpeg2video"); + if (!_codec) { + throw MPEG2CodecError("could not find codec"); + } + + _context = avcodec_alloc_context3(_codec); + if (!_context) { + throw MPEG2CodecError("could not allocate codec context"); + } + + int const r = avcodec_open2(_context, _codec, nullptr); + if (r < 0) { + avcodec_free_context(&_context); + throw MPEG2CodecError("could not open codec"); + } + + _decompressed_frame = av_frame_alloc(); + if (!_decompressed_frame) { + throw std::bad_alloc(); + } +} + + +MPEG2Decompressor::~MPEG2Decompressor() +{ + av_frame_free(&_decompressed_frame); +} + + +vector<FFmpegImage> +MPEG2Decompressor::decompress_frame(shared_ptr<const MonoMPEG2PictureFrame> frame) +{ + /* XXX: can we avoid this? */ + auto copy = av_malloc(frame->size() + AV_INPUT_BUFFER_PADDING_SIZE); + if (!copy) { + throw std::bad_alloc(); + } + memcpy(copy, frame->data(), frame->size()); + + AVPacket packet; + av_init_packet(&packet); + av_packet_from_data(&packet, reinterpret_cast<uint8_t*>(copy), frame->size()); + + auto images = decompress_packet(&packet); + + av_packet_unref(&packet); + + return images; +} + + +vector<FFmpegImage> +MPEG2Decompressor::flush() +{ + return decompress_packet(nullptr); +} + + +vector<FFmpegImage> +MPEG2Decompressor::decompress_packet(AVPacket* packet) +{ + int const r = avcodec_send_packet(_context, packet); + if (r < 0) { + throw MPEG2DecompressionError(String::compose("avcodec_send_packet failed (%1)", r)); + } + + vector<FFmpegImage> images; + while (true) { + int const r = avcodec_receive_frame(_context, _decompressed_frame); + if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) { + break; + } else if (r < 0) { + throw MPEG2DecompressionError("avcodec_receive_frame failed"); + } + + auto clone = av_frame_clone(_decompressed_frame); + if (!clone) { + throw std::bad_alloc(); + } + + images.push_back(FFmpegImage(clone)); + } + + return images; +} + + +MPEG2Compressor::MPEG2Compressor(dcp::Size size, int video_frame_rate, int64_t bit_rate) +{ + _codec = avcodec_find_encoder_by_name("mpeg2video"); + if (!_codec) { + throw MPEG2CodecError("could not find codec"); + } + + _context = avcodec_alloc_context3(_codec); + if (!_context) { + throw MPEG2CodecError("could not allocate codec context"); + } + + _context->width = size.width; + _context->height = size.height; + _context->time_base = AVRational{1, video_frame_rate}; + _context->pix_fmt = AV_PIX_FMT_YUV420P; + _context->bit_rate = bit_rate; + + int const r = avcodec_open2(_context, _codec, nullptr); + if (r < 0) { + avcodec_free_context(&_context); + throw MPEG2CodecError("could not open codec"); + } +} + + +optional<MPEG2Compressor::IndexedFrame> +MPEG2Compressor::send_and_receive(AVFrame const* frame) +{ + int r = avcodec_send_frame(_context, frame); + if (r < 0) { + throw MPEG2CompressionError(String::compose("avcodec_send_frame failed (%1", r)); + } + + auto packet = av_packet_alloc(); + if (!packet) { + throw MPEG2CompressionError("could not allocate packet"); + } + + r = avcodec_receive_packet(_context, packet); + if (r < 0 && r != AVERROR(EAGAIN)) { + throw MPEG2CompressionError(String::compose("avcodec_receive_packet failed (%1)", r)); + } + + ScopeGuard sg = [&packet]() { + av_packet_free(&packet); + }; + + if (packet->size == 0) { + return {}; + } + + DCP_ASSERT(_context->time_base.num == 1); + return IndexedFrame{make_shared<MonoMPEG2PictureFrame>(packet->data, packet->size), std::round(static_cast<double>(packet->pts) / _context->time_base.den)}; +} + + +optional<MPEG2Compressor::IndexedFrame> +MPEG2Compressor::compress_frame(FFmpegImage const& image) +{ + return send_and_receive(image.frame()); +} + + +optional<MPEG2Compressor::IndexedFrame> +MPEG2Compressor::flush() +{ + return send_and_receive(nullptr); +} diff --git a/src/mpeg2_transcode.h b/src/mpeg2_transcode.h new file mode 100644 index 00000000..1f13cfc3 --- /dev/null +++ b/src/mpeg2_transcode.h @@ -0,0 +1,108 @@ +/* + Copyright (C) 2023 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. +*/ + + +#ifndef LIBDCP_MPEG2_TRANSCODE_H +#define LIBDCP_MPEG2_TRANSCODE_H + + +#include "ffmpeg_image.h" +#include <memory> + + +struct AVCodec; +struct AVCodecContext; +struct AVFrame; +struct AVPacket; + + +namespace dcp { + + +class MonoMPEG2PictureFrame; + + +class MPEG2Codec +{ +public: + MPEG2Codec() = default; + virtual ~MPEG2Codec(); + + MPEG2Codec(MPEG2Codec const&) = delete; + MPEG2Codec& operator=(MPEG2Codec const&) = delete; + +protected: + AVCodec const* _codec; + AVCodecContext* _context; +}; + + +class MPEG2Decompressor : public MPEG2Codec +{ +public: + MPEG2Decompressor(); + ~MPEG2Decompressor(); + + std::vector<FFmpegImage> decompress_frame(std::shared_ptr<const MonoMPEG2PictureFrame> frame); + std::vector<FFmpegImage> flush(); + +private: + std::vector<FFmpegImage> decompress_packet(AVPacket* packet); + + AVFrame* _decompressed_frame; +}; + + +class MPEG2Compressor : public MPEG2Codec +{ +public: + MPEG2Compressor(dcp::Size size, int video_frame_rate, int64_t bit_rate); + + MPEG2Compressor(MPEG2Compressor const&) = delete; + MPEG2Compressor& operator=(MPEG2Compressor const&) = delete; + + /** Frame data with frame index within the asset */ + typedef std::pair<std::shared_ptr<MonoMPEG2PictureFrame>, int64_t> IndexedFrame; + + boost::optional<IndexedFrame> compress_frame(FFmpegImage const& image); + boost::optional<IndexedFrame> flush(); + +private: + boost::optional<IndexedFrame> send_and_receive(AVFrame const* frame); +}; + + +} + + +#endif @@ -64,7 +64,8 @@ namespace dcp class MXFMetadata; -class PictureAssetWriter; +class J2KPictureAssetWriter; +class MPEG2PictureAssetWriter; /** @class MXF @@ -136,7 +137,9 @@ public: protected: template <class P, class Q> - friend void start (PictureAssetWriter* writer, std::shared_ptr<P> state, Q* mxf, uint8_t const * data, int size); + friend void start (J2KPictureAssetWriter* writer, std::shared_ptr<P> state, Q* mxf, uint8_t const * data, int size); + template <class P, class Q> + friend void start (MPEG2PictureAssetWriter* writer, std::shared_ptr<P> state, Q* mxf, uint8_t const * data, int size); MXF (); diff --git a/src/name_format.h b/src/name_format.h index 6401fe82..3eb0277b 100644 --- a/src/name_format.h +++ b/src/name_format.h @@ -37,8 +37,8 @@ */ -#ifndef LIBDCP_NAME_FORMAT -#define LIBDCP_NAME_FORMAT +#ifndef LIBDCP_NAME_FORMAT_H +#define LIBDCP_NAME_FORMAT_H #include <string> diff --git a/src/picture_asset.cc b/src/picture_asset.cc index c9f669a6..3b28bf4b 100644 --- a/src/picture_asset.cc +++ b/src/picture_asset.cc @@ -32,199 +32,24 @@ */ -/** @file src/picture_asset.cc - * @brief PictureAsset class - */ - - -#include "compose.hpp" -#include "dcp_assert.h" -#include "equality_options.h" -#include "exceptions.h" -#include "j2k_transcode.h" -#include "openjpeg_image.h" #include "picture_asset.h" -#include "picture_asset_writer.h" -#include "util.h" -#include <asdcp/AS_DCP.h> -#include <asdcp/KM_fileio.h> -#include <libxml++/nodes/element.h> -#include <boost/filesystem.hpp> -#include <list> -#include <stdexcept> - - -using std::string; -using std::list; -using std::vector; -using std::max; -using std::pair; -using std::make_pair; -using std::shared_ptr; -using namespace dcp; - - -PictureAsset::PictureAsset (boost::filesystem::path file) - : Asset (file) -{ - -} - -PictureAsset::PictureAsset (Fraction edit_rate, Standard standard) - : MXF (standard) - , _edit_rate (edit_rate) -{ -} +using namespace dcp; -void -PictureAsset::read_picture_descriptor (ASDCP::JP2K::PictureDescriptor const & desc) +PictureAsset::PictureAsset(boost::filesystem::path file) + : Asset(file) { - _size.width = desc.StoredWidth; - _size.height = desc.StoredHeight; - _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator); - _intrinsic_duration = desc.ContainerDuration; - _frame_rate = Fraction (desc.SampleRate.Numerator, desc.SampleRate.Denominator); - _screen_aspect_ratio = Fraction (desc.AspectRatio.Numerator, desc.AspectRatio.Denominator); -} - -bool -PictureAsset::descriptor_equals ( - ASDCP::JP2K::PictureDescriptor const & a, ASDCP::JP2K::PictureDescriptor const & b, NoteHandler note - ) const -{ - if ( - a.EditRate != b.EditRate || - a.SampleRate != b.SampleRate || - a.StoredWidth != b.StoredWidth || - a.StoredHeight != b.StoredHeight || - a.AspectRatio != b.AspectRatio || - a.Rsize != b.Rsize || - a.Xsize != b.Xsize || - a.Ysize != b.Ysize || - a.XOsize != b.XOsize || - a.YOsize != b.YOsize || - a.XTsize != b.XTsize || - a.YTsize != b.YTsize || - a.XTOsize != b.XTOsize || - a.YTOsize != b.YTOsize || - a.Csize != b.Csize -// a.CodingStyleDefault != b.CodingStyleDefault || -// a.QuantizationDefault != b.QuantizationDefault - ) { - - note (NoteType::ERROR, "video MXF picture descriptors differ"); - return false; - } - - if (a.ContainerDuration != b.ContainerDuration) { - note (NoteType::ERROR, "video container durations differ"); - } - -// for (unsigned int j = 0; j < ASDCP::JP2K::MaxComponents; ++j) { -// if (a.ImageComponents[j] != b.ImageComponents[j]) { -// notes.pack_start ("video MXF picture descriptors differ"); -// } -// } - - return true; } -bool -PictureAsset::frame_buffer_equals ( - int frame, EqualityOptions const& opt, NoteHandler note, - uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B - ) const +PictureAsset::PictureAsset(Fraction edit_rate, Standard standard) + : MXF(standard) + , _edit_rate(edit_rate) { - if (size_A == size_B && memcmp (data_A, data_B, size_A) == 0) { - note (NoteType::NOTE, "J2K identical"); - /* Easy result; the J2K data is identical */ - return true; - } - - /* Decompress the images to bitmaps */ - auto image_A = decompress_j2k (const_cast<uint8_t*>(data_A), size_A, 0); - auto image_B = decompress_j2k (const_cast<uint8_t*>(data_B), size_B, 0); - - /* Compare them */ - - vector<int> abs_diffs (image_A->size().width * image_A->size().height * 3); - int d = 0; - int max_diff = 0; - for (int c = 0; c < 3; ++c) { - - if (image_A->size() != image_B->size()) { - note (NoteType::ERROR, String::compose ("image sizes for frame %1 differ", frame)); - return false; - } - - int const pixels = image_A->size().width * image_A->size().height; - for (int j = 0; j < pixels; ++j) { - int const t = abs (image_A->data(c)[j] - image_B->data(c)[j]); - abs_diffs[d++] = t; - max_diff = max (max_diff, t); - } - } - - uint64_t total = 0; - for (vector<int>::iterator j = abs_diffs.begin(); j != abs_diffs.end(); ++j) { - total += *j; - } - - double const mean = double (total) / abs_diffs.size (); - - uint64_t total_squared_deviation = 0; - for (auto j: abs_diffs) { - total_squared_deviation += pow (j - mean, 2); - } - - auto const std_dev = sqrt (double (total_squared_deviation) / abs_diffs.size()); - - note (NoteType::NOTE, String::compose("mean difference %1 deviation %2", mean, std_dev)); - - if (mean > opt.max_mean_pixel_error) { - note ( - NoteType::ERROR, - String::compose ("mean %1 out of range %2 in frame %3", mean, opt.max_mean_pixel_error, frame) - ); - - return false; - } - - if (std_dev > opt.max_std_dev_pixel_error) { - note ( - NoteType::ERROR, - String::compose ("standard deviation %1 out of range %2 in frame %3", std_dev, opt.max_std_dev_pixel_error, frame) - ); - - return false; - } - - return true; } -string -PictureAsset::static_pkl_type (Standard standard) -{ - switch (standard) { - case Standard::INTEROP: - return "application/x-smpte-mxf;asdcpKind=Picture"; - case Standard::SMPTE: - return "application/mxf"; - default: - DCP_ASSERT (false); - } -} - - -string -PictureAsset::pkl_type (Standard standard) const -{ - return static_pkl_type (standard); -} diff --git a/src/picture_asset.h b/src/picture_asset.h index 9ad1eb22..36364485 100644 --- a/src/picture_asset.h +++ b/src/picture_asset.h @@ -32,56 +32,31 @@ */ -/** @file src/picture_asset.h - * @brief PictureAsset class - */ - - #ifndef LIBDCP_PICTURE_ASSET_H #define LIBDCP_PICTURE_ASSET_H +#include "asset.h" #include "mxf.h" -#include "util.h" -#include "metadata.h" - - -namespace ASDCP { - namespace JP2K { - struct PictureDescriptor; - } -} +#include "types.h" namespace dcp { -class MonoPictureFrame; -class StereoPictureFrame; -class PictureAssetWriter; - - -/** @class PictureAsset - * @brief An asset made up of JPEG2000 data - */ class PictureAsset : public Asset, public MXF { public: - /** Load a PictureAsset from a file */ - explicit PictureAsset (boost::filesystem::path file); - - /** Create a new PictureAsset with a given edit rate and standard */ + explicit PictureAsset(boost::filesystem::path file); PictureAsset(Fraction edit_rate, Standard standard); - enum class Behaviour { - OVERWRITE_EXISTING, - MAKE_NEW - }; + Fraction edit_rate () const { + return _edit_rate; + } - virtual std::shared_ptr<PictureAssetWriter> start_write ( - boost::filesystem::path file, - Behaviour behaviour - ) = 0; + int64_t intrinsic_duration () const { + return _intrinsic_duration; + } Size size () const { return _size; @@ -107,33 +82,7 @@ public: _screen_aspect_ratio = r; } - Fraction edit_rate () const { - return _edit_rate; - } - - int64_t intrinsic_duration () const { - return _intrinsic_duration; - } - - static std::string static_pkl_type (Standard standard); - protected: - friend class MonoPictureAssetWriter; - friend class StereoPictureAssetWriter; - - bool frame_buffer_equals ( - int frame, EqualityOptions const& opt, NoteHandler note, - uint8_t const * data_A, unsigned int size_A, uint8_t const * data_B, unsigned int size_B - ) const; - - bool descriptor_equals ( - ASDCP::JP2K::PictureDescriptor const & a, - ASDCP::JP2K::PictureDescriptor const & b, - NoteHandler note - ) const; - - void read_picture_descriptor (ASDCP::JP2K::PictureDescriptor const &); - Fraction _edit_rate; /** The total length of this content in video frames. The amount of * content presented may be less than this. @@ -144,8 +93,6 @@ protected: Fraction _frame_rate; Fraction _screen_aspect_ratio; -private: - std::string pkl_type (Standard standard) const override; }; @@ -153,3 +100,4 @@ private: #endif + @@ -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); } } @@ -32,7 +32,7 @@ */ -/** @file src/pkl.cc +/** @file src/pkl.h * @brief PKL class */ 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..acd7c7fc 100644 --- a/src/reel.cc +++ b/src/reel.cc @@ -41,8 +41,8 @@ #include "decrypted_kdm_key.h" #include "equality_options.h" #include "interop_subtitle_asset.h" -#include "mono_picture_asset.h" -#include "picture_asset.h" +#include "mono_j2k_picture_asset.h" +#include "j2k_picture_asset.h" #include "reel.h" #include "reel_atmos_asset.h" #include "reel_closed_caption_asset.h" @@ -57,7 +57,7 @@ #include "reel_subtitle_asset.h" #include "smpte_subtitle_asset.h" #include "sound_asset.h" -#include "stereo_picture_asset.h" +#include "stereo_j2k_picture_asset.h" #include "subtitle_asset.h" #include "util.h" #include <libxml++/nodes/element.h> @@ -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); @@ -327,7 +327,7 @@ Reel::give_kdm_to_assets (DecryptedKDM const & kdm) { for (auto const& i: kdm.keys()) { if (_main_picture && i.id() == _main_picture->key_id() && _main_picture->asset_ref().resolved()) { - _main_picture->asset()->set_key (i.key()); + _main_picture->j2k_asset()->set_key(i.key()); } if (_main_sound && i.id() == _main_sound->key_id() && _main_sound->asset_ref().resolved()) { _main_sound->asset()->set_key (i.key()); @@ -32,7 +32,7 @@ */ -/** @file src/reel.cc +/** @file src/reel.h * @brief Reel class */ 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..e2b65585 100644 --- a/src/reel_markers_asset.h +++ b/src/reel_markers_asset.h @@ -32,7 +32,7 @@ */ -/** @file src/reel_markers_asset.cc +/** @file src/reel_markers_asset.h * @brief ReelMarkersAsset class */ @@ -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_mono_picture_asset.cc b/src/reel_mono_picture_asset.cc index 9c44ec20..81eb4df9 100644 --- a/src/reel_mono_picture_asset.cc +++ b/src/reel_mono_picture_asset.cc @@ -38,7 +38,7 @@ #include "reel_mono_picture_asset.h" -#include "mono_picture_asset.h" +#include "mono_j2k_picture_asset.h" #include <libcxml/cxml.h> @@ -47,7 +47,7 @@ using std::shared_ptr; using namespace dcp; -ReelMonoPictureAsset::ReelMonoPictureAsset (std::shared_ptr<MonoPictureAsset> asset, int64_t entry_point) +ReelMonoPictureAsset::ReelMonoPictureAsset(std::shared_ptr<PictureAsset> asset, int64_t entry_point) : ReelPictureAsset (asset, entry_point) { diff --git a/src/reel_mono_picture_asset.h b/src/reel_mono_picture_asset.h index fb2dff70..13c6545e 100644 --- a/src/reel_mono_picture_asset.h +++ b/src/reel_mono_picture_asset.h @@ -42,13 +42,14 @@ #include "reel_picture_asset.h" -#include "mono_picture_asset.h" +#include "mono_j2k_picture_asset.h" +#include "mono_mpeg2_picture_asset.h" namespace dcp { -class MonoPictureAsset; +class MonoJ2KPictureAsset; /** @class ReelMonoPictureAsset @@ -57,17 +58,27 @@ class MonoPictureAsset; class ReelMonoPictureAsset : public ReelPictureAsset { public: - ReelMonoPictureAsset (std::shared_ptr<MonoPictureAsset> asset, int64_t entry_point); + ReelMonoPictureAsset(std::shared_ptr<PictureAsset> asset, int64_t entry_point); explicit ReelMonoPictureAsset (std::shared_ptr<const cxml::Node>); - /** @return the MonoPictureAsset that this object refers to */ - std::shared_ptr<const MonoPictureAsset> mono_asset () const { - return asset_of_type<const MonoPictureAsset>(); + /** @return the MonoJ2KPictureAsset that this object refers to, if applicable */ + std::shared_ptr<const MonoJ2KPictureAsset> mono_j2k_asset() const { + return asset_of_type<const MonoJ2KPictureAsset>(); } - /** @return the MonoPictureAsset that this object refers to */ - std::shared_ptr<MonoPictureAsset> mono_asset () { - return asset_of_type<MonoPictureAsset>(); + /** @return the MonoJ2KPictureAsset that this object refers to */ + std::shared_ptr<MonoJ2KPictureAsset> mono_j2k_asset() { + return asset_of_type<MonoJ2KPictureAsset>(); + } + + /** @return the MonoMPEG2PictureAsset that this object refers to, if applicable */ + std::shared_ptr<const MonoMPEG2PictureAsset> mono_mpeg2_asset() const { + return asset_of_type<const MonoMPEG2PictureAsset>(); + } + + /** @return the MonoMPEG2PictureAsset that this object refers to */ + std::shared_ptr<MonoMPEG2PictureAsset> mono_mpeg2_asset() { + return asset_of_type<MonoMPEG2PictureAsset>(); } private: diff --git a/src/reel_picture_asset.cc b/src/reel_picture_asset.cc index eb87d039..37a6bfcc 100644 --- a/src/reel_picture_asset.cc +++ b/src/reel_picture_asset.cc @@ -39,7 +39,7 @@ #include "compose.hpp" #include "dcp_assert.h" -#include "picture_asset.h" +#include "j2k_picture_asset.h" #include "raw_convert.h" #include "reel_picture_asset.h" #include "warnings.h" @@ -59,7 +59,7 @@ using boost::optional; using namespace dcp; -ReelPictureAsset::ReelPictureAsset (shared_ptr<PictureAsset> asset, int64_t entry_point) +ReelPictureAsset::ReelPictureAsset(shared_ptr<PictureAsset> asset, int64_t entry_point) : ReelFileAsset (asset, asset->key_id(), asset->id(), asset->edit_rate(), asset->intrinsic_duration(), entry_point) , _frame_rate (asset->frame_rate ()) , _screen_aspect_ratio (asset->screen_aspect_ratio ()) @@ -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..9f42a5b6 100644 --- a/src/reel_picture_asset.h +++ b/src/reel_picture_asset.h @@ -42,7 +42,8 @@ #include "reel_file_asset.h" -#include "picture_asset.h" +#include "j2k_picture_asset.h" +#include "mpeg2_picture_asset.h" namespace dcp { @@ -54,20 +55,41 @@ namespace dcp { class ReelPictureAsset : public ReelFileAsset { public: - ReelPictureAsset (std::shared_ptr<PictureAsset> asset, int64_t entry_point); + ReelPictureAsset(std::shared_ptr<PictureAsset> asset, int64_t entry_point); explicit ReelPictureAsset (std::shared_ptr<const cxml::Node>); - /** @return the PictureAsset that this object refers to */ - std::shared_ptr<const PictureAsset> asset () const { + /** @return the PictureAsset that this object refers to, if applicable */ + std::shared_ptr<const PictureAsset> asset() const { return asset_of_type<const PictureAsset>(); } - /** @return the PictureAsset that this object refers to */ - std::shared_ptr<PictureAsset> asset () { + /** @return the PictureAsset that this object refers to, if applicable */ + std::shared_ptr<PictureAsset> asset() { return asset_of_type<PictureAsset>(); } - virtual xmlpp::Node* write_to_cpl (xmlpp::Node* node, Standard standard) const override; + /** @return the J2KPictureAsset that this object refers to, if applicable */ + std::shared_ptr<const J2KPictureAsset> j2k_asset() const { + return asset_of_type<const J2KPictureAsset>(); + } + + /** @return the J2KPictureAsset that this object refers to, if applicable */ + std::shared_ptr<J2KPictureAsset> j2k_asset() { + return asset_of_type<J2KPictureAsset>(); + } + + /** @return the MPEG2PictureAsset that this object refers to, if applicable */ + std::shared_ptr<const MPEG2PictureAsset> mpeg2_asset() const { + return asset_of_type<const MPEG2PictureAsset>(); + } + + /** @return the MPEG2PictureAsset that this object refers to, if applicable */ + std::shared_ptr<MPEG2PictureAsset> mpeg2_asset() { + return asset_of_type<MPEG2PictureAsset>(); + } + + 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_smpte_subtitle_asset.h b/src/reel_smpte_subtitle_asset.h index 2a097309..49b6000b 100644 --- a/src/reel_smpte_subtitle_asset.h +++ b/src/reel_smpte_subtitle_asset.h @@ -32,8 +32,8 @@ */ -/** @file src/reel_interop_subtitle_asset.h - * @brief ReelInteropSubtitleAsset class +/** @file src/reel_smpte_subtitle_asset.h + * @brief ReelSMPTESubtitleAsset class */ diff --git a/src/reel_stereo_picture_asset.cc b/src/reel_stereo_picture_asset.cc index 5bf4756c..13bfec8e 100644 --- a/src/reel_stereo_picture_asset.cc +++ b/src/reel_stereo_picture_asset.cc @@ -38,7 +38,7 @@ #include "reel_stereo_picture_asset.h" -#include "stereo_picture_asset.h" +#include "stereo_j2k_picture_asset.h" #include <libcxml/cxml.h> @@ -49,7 +49,7 @@ using std::shared_ptr; using namespace dcp; -ReelStereoPictureAsset::ReelStereoPictureAsset (std::shared_ptr<StereoPictureAsset> mxf, int64_t entry_point) +ReelStereoPictureAsset::ReelStereoPictureAsset (std::shared_ptr<StereoJ2KPictureAsset> mxf, int64_t entry_point) : ReelPictureAsset (mxf, entry_point) { diff --git a/src/reel_stereo_picture_asset.h b/src/reel_stereo_picture_asset.h index 7cac1c8b..09170ddd 100644 --- a/src/reel_stereo_picture_asset.h +++ b/src/reel_stereo_picture_asset.h @@ -42,13 +42,13 @@ #include "reel_picture_asset.h" -#include "stereo_picture_asset.h" +#include "stereo_j2k_picture_asset.h" namespace dcp { -class StereoPictureAsset; +class StereoJ2KPictureAsset; /** @class ReelStereoPictureAsset @@ -57,17 +57,17 @@ class StereoPictureAsset; class ReelStereoPictureAsset : public ReelPictureAsset { public: - ReelStereoPictureAsset (std::shared_ptr<StereoPictureAsset> content, int64_t entry_point); + ReelStereoPictureAsset (std::shared_ptr<StereoJ2KPictureAsset> content, int64_t entry_point); explicit ReelStereoPictureAsset (std::shared_ptr<const cxml::Node>); - /** @return the StereoPictureAsset that this object refers to */ - std::shared_ptr<const StereoPictureAsset> stereo_asset () const { - return asset_of_type<const StereoPictureAsset>(); + /** @return the StereoJ2KPictureAsset that this object refers to */ + std::shared_ptr<const StereoJ2KPictureAsset> stereo_asset () const { + return asset_of_type<const StereoJ2KPictureAsset>(); } - /** @return the StereoPictureAsset that this object refers to */ - std::shared_ptr<StereoPictureAsset> stereo_asset () { - return asset_of_type<StereoPictureAsset>(); + /** @return the StereoJ2KPictureAsset that this object refers to */ + std::shared_ptr<StereoJ2KPictureAsset> stereo_asset () { + return asset_of_type<StereoJ2KPictureAsset>(); } 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/rgb_xyz.h b/src/rgb_xyz.h index c41fdee0..315295ea 100644 --- a/src/rgb_xyz.h +++ b/src/rgb_xyz.h @@ -32,7 +32,7 @@ */ -/** @file rgb_xyz.h +/** @file src/rgb_xyz.h * @brief Conversion between RGB and XYZ */ 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..c73255b8 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)); @@ -82,6 +83,7 @@ SoundAsset::SoundAsset (boost::filesystem::path file) } _sampling_rate = desc.AudioSamplingRate.Denominator ? (desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator) : 0; + _bit_depth = desc.QuantizationBits; _channels = desc.ChannelCount; _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator); @@ -138,14 +140,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 +281,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/sound_asset.h b/src/sound_asset.h index e69c3988..e5acb119 100644 --- a/src/sound_asset.h +++ b/src/sound_asset.h @@ -115,6 +115,13 @@ public: return _sampling_rate; } + /** @return Bit depth of samples. This should always be 24, but we return it + * so the verification code can check + */ + int bit_depth() const { + return _bit_depth; + } + Fraction edit_rate () const { return _edit_rate; } @@ -148,6 +155,7 @@ private: int _channels = 0; ///< number of channels in the MXF boost::optional<int> _active_channels; ///< estimate of the number of active channels int _sampling_rate = 0; ///< sampling rate in Hz + int _bit_depth = 24; boost::optional<std::string> _language; }; diff --git a/src/sound_frame.cc b/src/sound_frame.cc index 25845d88..4d90c9c2 100644 --- a/src/sound_frame.cc +++ b/src/sound_frame.cc @@ -37,6 +37,7 @@ */ +#include "dcp_assert.h" #include "sound_frame.h" #include <asdcp/AS_DCP.h> #include <iostream> @@ -52,15 +53,28 @@ SoundFrame::SoundFrame (ASDCP::PCM::MXFReader* reader, int n, std::shared_ptr<co ASDCP::PCM::AudioDescriptor desc; reader->FillAudioDescriptor (desc); _channels = desc.ChannelCount; + _bits = desc.QuantizationBits; } int32_t SoundFrame::get (int channel, int frame) const { - uint8_t const * d = data() + (frame * _channels * 3) + (channel * 3); - /* This is slightly dubious I think */ - return (d[0] << 8 | (d[1] << 16) | (d[2] << 24)) >> 8; + switch (_bits) { + case 24: + { + uint8_t const * d = data() + (frame * _channels * 3) + (channel * 3); + /* This is slightly dubious I think */ + return (d[0] << 8 | (d[1] << 16) | (d[2] << 24)) >> 8; + } + case 16: + { + uint8_t const * d = data() + (frame * _channels * 2) + (channel * 2); + return d[0] | (d[1] << 8); + } + default: + DCP_ASSERT(false); + } } @@ -74,5 +88,5 @@ SoundFrame::channels () const int SoundFrame::samples () const { - return size() / (_channels * 3); + return size() / (_channels * _bits / 8); } diff --git a/src/sound_frame.h b/src/sound_frame.h index 0f5021e2..72ab5925 100644 --- a/src/sound_frame.h +++ b/src/sound_frame.h @@ -54,10 +54,16 @@ public: SoundFrame (ASDCP::PCM::MXFReader* reader, int n, std::shared_ptr<const DecryptionContext> c, bool check_hmac); int channels () const; int samples () const; + + int bits() const { + return _bits; + } + int32_t get (int channel, int sample) const; private: - int _channels = 0; + int _channels; + int _bits; }; diff --git a/src/stereo_picture_asset.cc b/src/stereo_j2k_picture_asset.cc index 2ce3cdc9..6a5e7d79 100644 --- a/src/stereo_picture_asset.cc +++ b/src/stereo_j2k_picture_asset.cc @@ -33,7 +33,7 @@ /** @file src/stereo_picture_asset.cc - * @brief StereoPictureAsset class + * @brief StereoJ2KPictureAsset class */ @@ -41,10 +41,10 @@ #include "equality_options.h" #include "exceptions.h" #include "filesystem.h" -#include "stereo_picture_asset.h" -#include "stereo_picture_asset_reader.h" -#include "stereo_picture_asset_writer.h" -#include "stereo_picture_frame.h" +#include "stereo_j2k_picture_asset.h" +#include "stereo_j2k_picture_asset_reader.h" +#include "stereo_j2k_picture_asset_writer.h" +#include "stereo_j2k_picture_frame.h" #include <asdcp/AS_DCP.h> @@ -56,10 +56,11 @@ using std::dynamic_pointer_cast; using namespace dcp; -StereoPictureAsset::StereoPictureAsset (boost::filesystem::path file) - : PictureAsset (file) +StereoJ2KPictureAsset::StereoJ2KPictureAsset (boost::filesystem::path file) + : J2KPictureAsset (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)); @@ -81,38 +82,39 @@ StereoPictureAsset::StereoPictureAsset (boost::filesystem::path file) } -StereoPictureAsset::StereoPictureAsset (Fraction edit_rate, Standard standard) - : PictureAsset (edit_rate, standard) +StereoJ2KPictureAsset::StereoJ2KPictureAsset (Fraction edit_rate, Standard standard) + : J2KPictureAsset (edit_rate, standard) { } -shared_ptr<PictureAssetWriter> -StereoPictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour) +shared_ptr<J2KPictureAssetWriter> +StereoJ2KPictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour) { - return shared_ptr<StereoPictureAssetWriter>(new StereoPictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING)); + return shared_ptr<StereoJ2KPictureAssetWriter>(new StereoJ2KPictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING)); } -shared_ptr<StereoPictureAssetReader> -StereoPictureAsset::start_read () const +shared_ptr<StereoJ2KPictureAssetReader> +StereoJ2KPictureAsset::start_read () const { - return shared_ptr<StereoPictureAssetReader> (new StereoPictureAssetReader(this, key(), standard())); + return shared_ptr<StereoJ2KPictureAssetReader> (new StereoJ2KPictureAssetReader(this, key(), standard())); } bool -StereoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const +StereoJ2KPictureAsset::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)) { @@ -132,7 +134,7 @@ StereoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& return false; } - auto other_picture = dynamic_pointer_cast<const StereoPictureAsset> (other); + auto other_picture = dynamic_pointer_cast<const StereoJ2KPictureAsset> (other); DCP_ASSERT (other_picture); auto reader = start_read (); @@ -141,8 +143,8 @@ StereoPictureAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& bool result = true; for (int i = 0; i < _intrinsic_duration; ++i) { - shared_ptr<const StereoPictureFrame> frame_A; - shared_ptr<const StereoPictureFrame> frame_B; + shared_ptr<const StereoJ2KPictureFrame> frame_A; + shared_ptr<const StereoJ2KPictureFrame> frame_B; try { frame_A = reader->get_frame (i); frame_B = other_reader->get_frame (i); diff --git a/src/stereo_picture_asset.h b/src/stereo_j2k_picture_asset.h index 6ee1d423..7ef86ec5 100644 --- a/src/stereo_picture_asset.h +++ b/src/stereo_j2k_picture_asset.h @@ -32,34 +32,34 @@ */ -/** @file src/stereo_picture_asset.h - * @brief StereoPictureAsset class +/** @file src/stereo_j2k_picture_asset.h + * @brief StereoJ2KPictureAsset class */ -#ifndef LIBDCP_STEREO_PICTURE_ASSET_H -#define LIBDCP_STEREO_PICTURE_ASSET_H +#ifndef LIBDCP_STEREO_J2K_PICTURE_ASSET_H +#define LIBDCP_STEREO_J2K_PICTURE_ASSET_H -#include "picture_asset.h" -#include "stereo_picture_asset_reader.h" +#include "j2k_picture_asset.h" +#include "stereo_j2k_picture_asset_reader.h" namespace dcp { -/** @class StereoPictureAsset +/** @class StereoJ2KPictureAsset * @brief A 3D (stereoscopic) picture asset */ -class StereoPictureAsset : public PictureAsset +class StereoJ2KPictureAsset : public J2KPictureAsset { public: - explicit StereoPictureAsset (boost::filesystem::path file); - explicit StereoPictureAsset (Fraction edit_rate, Standard standard); + explicit StereoJ2KPictureAsset (boost::filesystem::path file); + explicit StereoJ2KPictureAsset (Fraction edit_rate, Standard standard); - /** Start a progressive write to a StereoPictureAsset */ - std::shared_ptr<PictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override; - std::shared_ptr<StereoPictureAssetReader> start_read () const; + /** Start a progressive write to a StereoJ2KPictureAsset */ + std::shared_ptr<J2KPictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override; + std::shared_ptr<StereoJ2KPictureAssetReader> start_read () const; bool equals ( std::shared_ptr<const Asset> other, diff --git a/src/mono_picture_asset_reader.h b/src/stereo_j2k_picture_asset_reader.h index 5bead791..e4812a8e 100644 --- a/src/mono_picture_asset_reader.h +++ b/src/stereo_j2k_picture_asset_reader.h @@ -32,23 +32,23 @@ */ -/** @file src/mono_picture_asset_reader.h - * @brief MonoPictureAssetReader typedef +/** @file src/stereo_j2k_picture_asset_reader.h + * @brief StereoJ2KPictureAssetReader typedef */ -#ifndef LIBDCP_MONO_PICTURE_ASSET_READER_H -#define LIBDCP_MONO_PICTURE_ASSET_READER_H +#ifndef LIBDCP_STEREO_J2K_PICTURE_ASSET_READER_H +#define LIBDCP_STEREO_J2K_PICTURE_ASSET_READER_H #include "asset_reader.h" -#include "mono_picture_frame.h" +#include "stereo_j2k_picture_frame.h" namespace dcp { -typedef AssetReader<ASDCP::JP2K::MXFReader, MonoPictureFrame> MonoPictureAssetReader; +typedef AssetReader<ASDCP::JP2K::MXFSReader, StereoJ2KPictureFrame> StereoJ2KPictureAssetReader; } diff --git a/src/stereo_picture_asset_writer.cc b/src/stereo_j2k_picture_asset_writer.cc index 6ee271bc..e59de02f 100644 --- a/src/stereo_picture_asset_writer.cc +++ b/src/stereo_j2k_picture_asset_writer.cc @@ -33,20 +33,20 @@ /** @file src/stereo_picture_asset_writer.cc - * @brief StereoPictureAssetWriter class + * @brief StereoJ2KPictureAssetWriter class */ -#include "stereo_picture_asset_writer.h" +#include "stereo_j2k_picture_asset_writer.h" #include "exceptions.h" #include "dcp_assert.h" -#include "picture_asset.h" +#include "j2k_picture_asset.h" #include "crypto_context.h" #include <asdcp/AS_DCP.h> #include <asdcp/KM_fileio.h> -#include "picture_asset_writer_common.cc" +#include "j2k_picture_asset_writer_common.cc" using std::string; @@ -54,21 +54,21 @@ using std::shared_ptr; using namespace dcp; -struct StereoPictureAssetWriter::ASDCPState : public ASDCPStateBase +struct StereoJ2KPictureAssetWriter::ASDCPState : public ASDCPJ2KStateBase { ASDCP::JP2K::MXFSWriter mxf_writer; }; -StereoPictureAssetWriter::StereoPictureAssetWriter (PictureAsset* mxf, boost::filesystem::path file, bool overwrite) - : PictureAssetWriter (mxf, file, overwrite) - , _state (new StereoPictureAssetWriter::ASDCPState) +StereoJ2KPictureAssetWriter::StereoJ2KPictureAssetWriter (J2KPictureAsset* mxf, boost::filesystem::path file, bool overwrite) + : J2KPictureAssetWriter (mxf, file, overwrite) + , _state (new StereoJ2KPictureAssetWriter::ASDCPState) { } -StereoPictureAssetWriter::~StereoPictureAssetWriter() +StereoJ2KPictureAssetWriter::~StereoJ2KPictureAssetWriter() { try { /* Last-resort finalization to close the file, at least */ @@ -80,15 +80,15 @@ StereoPictureAssetWriter::~StereoPictureAssetWriter() void -StereoPictureAssetWriter::start (uint8_t const * data, int size) +StereoJ2KPictureAssetWriter::start (uint8_t const * data, int size) { dcp::start (this, _state, _picture_asset, data, size); _picture_asset->set_frame_rate (Fraction (_picture_asset->edit_rate().numerator * 2, _picture_asset->edit_rate().denominator)); } -FrameInfo -StereoPictureAssetWriter::write (uint8_t const * data, int size) +J2KFrameInfo +StereoJ2KPictureAssetWriter::write (uint8_t const * data, int size) { DCP_ASSERT (!_finalized); @@ -123,17 +123,17 @@ StereoPictureAssetWriter::write (uint8_t const * data, int size) ++_frames_written; } - return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash); + return J2KFrameInfo(before_offset, _state->mxf_writer.Tell() - before_offset, hash); } void -StereoPictureAssetWriter::fake_write (int size) +StereoJ2KPictureAssetWriter::fake_write(J2KFrameInfo const& info) { DCP_ASSERT (_started); DCP_ASSERT (!_finalized); - auto r = _state->mxf_writer.FakeWriteFrame (size, _next_eye == Eye::LEFT ? ASDCP::JP2K::SP_LEFT : ASDCP::JP2K::SP_RIGHT); + auto r = _state->mxf_writer.FakeWriteFrame(info.size, _next_eye == Eye::LEFT ? ASDCP::JP2K::SP_LEFT : ASDCP::JP2K::SP_RIGHT); if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError("error in writing video MXF", _file.string(), r)); } @@ -146,7 +146,7 @@ StereoPictureAssetWriter::fake_write (int size) bool -StereoPictureAssetWriter::finalize () +StereoJ2KPictureAssetWriter::finalize () { if (_started) { auto r = _state->mxf_writer.Finalize(); @@ -156,5 +156,5 @@ StereoPictureAssetWriter::finalize () } _picture_asset->_intrinsic_duration = _frames_written; - return PictureAssetWriter::finalize (); + return J2KPictureAssetWriter::finalize (); } diff --git a/src/stereo_picture_asset_writer.h b/src/stereo_j2k_picture_asset_writer.h index 1cee1202..e3f39a0b 100644 --- a/src/stereo_picture_asset_writer.h +++ b/src/stereo_j2k_picture_asset_writer.h @@ -32,12 +32,12 @@ */ -/** @file src/stereo_picture_asset_writer.h - * @brief StereoPictureAssetWriter class +/** @file src/stereo_j2k_picture_asset_writer.h + * @brief StereoJ2KPictureAssetWriter class */ -#include "picture_asset_writer.h" +#include "j2k_picture_asset_writer.h" #include <memory> #include <stdint.h> #include <string> @@ -46,33 +46,33 @@ namespace dcp { -/** @class StereoPictureAssetWriter - * @brief A helper class for writing to StereoPictureAssets. +/** @class StereoJ2KPictureAssetWriter + * @brief A helper class for writing to StereoJ2KPictureAssets. * - * Objects of this class can only be created with StereoPictureAsset::start_write(). + * Objects of this class can only be created with StereoJ2KPictureAsset::start_write(). * - * Frames can be written to the StereoPictureAsset by calling write() with a JPEG2000 image + * Frames can be written to the StereoJ2KPictureAsset by calling write() with a JPEG2000 image * (a verbatim .j2c file). finalize() should be called after the last frame has been written, * but if it is not, it will be called by the destructor (though in that case any error * during finalization will be ignored). */ -class StereoPictureAssetWriter : public PictureAssetWriter +class StereoJ2KPictureAssetWriter : public J2KPictureAssetWriter { public: - ~StereoPictureAssetWriter(); + ~StereoJ2KPictureAssetWriter(); /** Write a frame for one eye. Frames must be written left, then right, then left etc. * @param data JPEG2000 data. * @param size Size of data. */ - FrameInfo write (uint8_t const * data, int size) override; - void fake_write (int size) override; + J2KFrameInfo write(uint8_t const * data, int size) override; + void fake_write(J2KFrameInfo const& info) override; bool finalize () override; private: - friend class StereoPictureAsset; + friend class StereoJ2KPictureAsset; - StereoPictureAssetWriter (PictureAsset *, boost::filesystem::path file, bool); + StereoJ2KPictureAssetWriter (J2KPictureAsset *, boost::filesystem::path file, bool); void start (uint8_t const *, int); /* do this with an opaque pointer so we don't have to include diff --git a/src/stereo_picture_frame.cc b/src/stereo_j2k_picture_frame.cc index 8d3a2757..9ef91c5c 100644 --- a/src/stereo_picture_frame.cc +++ b/src/stereo_j2k_picture_frame.cc @@ -33,7 +33,7 @@ /** @file src/stereo_picture_frame.cc - * @brief StereoPictureFrame class + * @brief StereoJ2KPictureFrame class */ @@ -43,7 +43,7 @@ #include "exceptions.h" #include "j2k_transcode.h" #include "rgb_xyz.h" -#include "stereo_picture_frame.h" +#include "stereo_j2k_picture_frame.h" #include "util.h" #include <asdcp/AS_DCP.h> #include <asdcp/KM_fileio.h> @@ -55,7 +55,7 @@ using std::make_shared; using namespace dcp; -StereoPictureFrame::Part::Part (shared_ptr<ASDCP::JP2K::SFrameBuffer> buffer, Eye eye) +StereoJ2KPictureFrame::Part::Part (shared_ptr<ASDCP::JP2K::SFrameBuffer> buffer, Eye eye) : _buffer (buffer) , _eye (eye) { @@ -64,28 +64,28 @@ StereoPictureFrame::Part::Part (shared_ptr<ASDCP::JP2K::SFrameBuffer> buffer, Ey ASDCP::JP2K::FrameBuffer & -StereoPictureFrame::Part::mono () const +StereoJ2KPictureFrame::Part::mono () const { return _eye == Eye::LEFT ? _buffer->Left : _buffer->Right; } uint8_t const * -StereoPictureFrame::Part::data () const +StereoJ2KPictureFrame::Part::data () const { return mono().RoData(); } uint8_t * -StereoPictureFrame::Part::data () +StereoJ2KPictureFrame::Part::data () { return mono().Data(); } int -StereoPictureFrame::Part::size () const +StereoJ2KPictureFrame::Part::size () const { return mono().Size(); } @@ -96,7 +96,7 @@ StereoPictureFrame::Part::size () const * @param n Frame within the asset, not taking EntryPoint into account. * @param check_hmac true to check the HMAC and give an error if it is not as expected. */ -StereoPictureFrame::StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, shared_ptr<DecryptionContext> c, bool check_hmac) +StereoJ2KPictureFrame::StereoJ2KPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, shared_ptr<DecryptionContext> c, bool check_hmac) { /* XXX: unfortunate guesswork on this buffer size */ _buffer = make_shared<ASDCP::JP2K::SFrameBuffer>(4 * Kumu::Megabyte); @@ -107,7 +107,7 @@ StereoPictureFrame::StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, } -StereoPictureFrame::StereoPictureFrame () +StereoJ2KPictureFrame::StereoJ2KPictureFrame () { _buffer = make_shared<ASDCP::JP2K::SFrameBuffer>(4 * Kumu::Megabyte); } @@ -119,7 +119,7 @@ StereoPictureFrame::StereoPictureFrame () * reduction). */ shared_ptr<OpenJPEGImage> -StereoPictureFrame::xyz_image (Eye eye, int reduce) const +StereoJ2KPictureFrame::xyz_image (Eye eye, int reduce) const { switch (eye) { case Eye::LEFT: @@ -132,15 +132,15 @@ StereoPictureFrame::xyz_image (Eye eye, int reduce) const } -shared_ptr<StereoPictureFrame::Part> -StereoPictureFrame::right () const +shared_ptr<StereoJ2KPictureFrame::Part> +StereoJ2KPictureFrame::right () const { return make_shared<Part>(_buffer, Eye::RIGHT); } -shared_ptr<StereoPictureFrame::Part> -StereoPictureFrame::left () const +shared_ptr<StereoJ2KPictureFrame::Part> +StereoJ2KPictureFrame::left () const { return make_shared<Part>(_buffer, Eye::LEFT); } diff --git a/src/stereo_picture_frame.h b/src/stereo_j2k_picture_frame.h index b0c0f0c8..193960f3 100644 --- a/src/stereo_picture_frame.h +++ b/src/stereo_j2k_picture_frame.h @@ -32,13 +32,13 @@ */ -/** @file src/stereo_picture_frame.h - * @brief StereoPictureFrame class +/** @file src/stereo_j2k_picture_frame.h + * @brief StereoJ2KPictureFrame class */ -#ifndef LIBDCP_STEREO_PICTURE_FRAME_H -#define LIBDCP_STEREO_PICTURE_FRAME_H +#ifndef LIBDCP_STEREO_J2K_PICTURE_FRAME_H +#define LIBDCP_STEREO_J2K_PICTURE_FRAME_H #include "asset_reader.h" @@ -61,19 +61,19 @@ namespace dcp { class OpenJPEGImage; -class StereoPictureFrame; +class StereoJ2KPictureFrame; -/** @class StereoPictureFrame +/** @class StereoJ2KPictureFrame * @brief A single frame of a 3D (stereoscopic) picture asset */ -class StereoPictureFrame +class StereoJ2KPictureFrame { public: - StereoPictureFrame (); + StereoJ2KPictureFrame (); - StereoPictureFrame (StereoPictureFrame const &) = delete; - StereoPictureFrame& operator= (StereoPictureFrame const &) = delete; + StereoJ2KPictureFrame (StereoJ2KPictureFrame const &) = delete; + StereoJ2KPictureFrame& operator= (StereoJ2KPictureFrame const &) = delete; std::shared_ptr<OpenJPEGImage> xyz_image (Eye eye, int reduce = 0) const; @@ -87,7 +87,7 @@ public: int size () const override; private: - friend class StereoPictureFrame; + friend class StereoJ2KPictureFrame; ASDCP::JP2K::FrameBuffer& mono () const; @@ -99,12 +99,12 @@ public: std::shared_ptr<Part> right () const; private: - /* XXX: this is a bit of a shame, but I tried friend StereoPictureAssetReader and it's + /* XXX: this is a bit of a shame, but I tried friend StereoJ2KPictureAssetReader and it's rejected by some (seemingly older) GCCs. */ - friend class AssetReader<ASDCP::JP2K::MXFSReader, StereoPictureFrame>; + friend class AssetReader<ASDCP::JP2K::MXFSReader, StereoJ2KPictureFrame>; - StereoPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac); + StereoJ2KPictureFrame (ASDCP::JP2K::MXFSReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac); std::shared_ptr<ASDCP::JP2K::SFrameBuffer> _buffer; }; 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 e2165548..d58256a5 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 ec8925f2..1e27514a 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -43,8 +43,8 @@ #include "exceptions.h" #include "filesystem.h" #include "interop_subtitle_asset.h" -#include "mono_picture_asset.h" -#include "mono_picture_frame.h" +#include "mono_j2k_picture_asset.h" +#include "mono_j2k_picture_frame.h" #include "raw_convert.h" #include "reel.h" #include "reel_closed_caption_asset.h" @@ -55,8 +55,8 @@ #include "reel_smpte_subtitle_asset.h" #include "reel_subtitle_asset.h" #include "smpte_subtitle_asset.h" -#include "stereo_picture_asset.h" -#include "stereo_picture_frame.h" +#include "stereo_j2k_picture_asset.h" +#include "stereo_j2k_picture_frame.h" #include "verify.h" #include "verify_j2k.h" #include <libxml/parserInternals.h> @@ -89,6 +89,7 @@ using std::cout; using std::dynamic_pointer_cast; +using std::function; using std::list; using std::make_shared; using std::map; @@ -98,7 +99,6 @@ using std::shared_ptr; using std::string; using std::vector; using boost::optional; -using boost::function; using namespace dcp; @@ -294,9 +294,83 @@ parse (XercesDOMParser& parser, string xml) } +class Context +{ +public: + Context( + std::vector<VerificationNote>& notes_, + boost::filesystem::path xsd_dtd_directory_, + function<void (string, optional<boost::filesystem::path>)> stage_, + function<void (float)> progress_, + VerificationOptions options_ + ) + : notes(notes_) + , xsd_dtd_directory(xsd_dtd_directory_) + , stage(stage_) + , progress(progress_) + , options(options_) + { + + } + + Context(Context const&) = delete; + Context& operator=(Context const&) = delete; + + template<typename... Args> + void ok(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::OK, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void warning(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::WARNING, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void bv21_error(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::BV21_ERROR, code, std::forward<Args>(args)...}); + } + + template<typename... Args> + void error(dcp::VerificationNote::Code code, Args... args) + { + add_note({dcp::VerificationNote::Type::ERROR, code, std::forward<Args>(args)...}); + } + + void add_note(dcp::VerificationNote note) + { + if (cpl) { + note.set_cpl_id(cpl->id()); + } + notes.push_back(std::move(note)); + } + + void add_note_if_not_existing(dcp::VerificationNote note) + { + if (find(notes.begin(), notes.end(), note) == notes.end()) { + add_note(note); + } + } + + std::vector<VerificationNote>& notes; + std::shared_ptr<const DCP> dcp; + std::shared_ptr<const CPL> cpl; + boost::filesystem::path xsd_dtd_directory; + function<void (string, optional<boost::filesystem::path>)> stage; + function<void (float)> progress; + VerificationOptions options; + + boost::optional<string> subtitle_language; + boost::optional<int> audio_channels; +}; + + template <class T> void -validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<VerificationNote>& notes) +validate_xml(Context& context, T xml) { try { XMLPlatformUtils::Initialize (); @@ -349,7 +423,7 @@ validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<Verificat parser.setValidationSchemaFullChecking(true); parser.setErrorHandler(&error_handler); - LocalFileResolver resolver (xsd_dtd_directory); + LocalFileResolver resolver(context.xsd_dtd_directory); parser.setEntityResolver(&resolver); try { @@ -367,13 +441,12 @@ validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, vector<Verificat XMLPlatformUtils::Terminate (); for (auto i: error_handler.errors()) { - notes.push_back ({ - VerificationNote::Type::ERROR, + context.error( VerificationNote::Code::INVALID_XML, i.message(), boost::trim_copy(i.public_id() + " " + i.system_id()), i.line() - }); + ); } } @@ -387,9 +460,8 @@ enum class VerifyAssetResult { static VerifyAssetResult verify_asset( - shared_ptr<const DCP> dcp, + Context& context, shared_ptr<const ReelFileAsset> reel_file_asset, - function<void (float)> progress, string* reference_hash, string* calculated_hash ) @@ -403,11 +475,11 @@ verify_asset( * call to hash(). */ reel_file_asset->asset_ref()->unset_hash(); - *calculated_hash = reel_file_asset->asset_ref()->hash([progress](int64_t done, int64_t total) { - progress(float(done) / total); + *calculated_hash = reel_file_asset->asset_ref()->hash([&context](int64_t done, int64_t total) { + context.progress(float(done) / total); }); - auto pkls = dcp->pkls(); + auto pkls = context.dcp->pkls(); /* We've read this DCP in so it must have at least one PKL */ DCP_ASSERT (!pkls.empty()); @@ -437,103 +509,106 @@ verify_asset( } -void -verify_language_tag (string tag, vector<VerificationNote>& notes) +static void +verify_language_tag(Context& context, string tag) { try { LanguageTag test (tag); } catch (LanguageTagError &) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_LANGUAGE, tag}); + context.bv21_error(VerificationNote::Code::INVALID_LANGUAGE, tag); } } static void -verify_picture_asset(shared_ptr<const ReelFileAsset> reel_file_asset, boost::filesystem::path file, int64_t start_frame, vector<VerificationNote>& notes, function<void (float)> progress) +verify_picture_asset( + Context& context, + shared_ptr<const ReelFileAsset> reel_file_asset, + boost::filesystem::path file, + int64_t start_frame + ) { - auto asset = dynamic_pointer_cast<PictureAsset>(reel_file_asset->asset_ref().asset()); + auto asset = dynamic_pointer_cast<J2KPictureAsset>(reel_file_asset->asset_ref().asset()); auto const duration = asset->intrinsic_duration (); - auto check_and_add = [¬es](vector<VerificationNote> const& j2k_notes) { + auto check_and_add = [&context](vector<VerificationNote> const& j2k_notes) { for (auto i: j2k_notes) { - if (find(notes.begin(), notes.end(), i) == notes.end()) { - notes.push_back (i); - } + context.add_note_if_not_existing(i); } }; int const max_frame = rint(250 * 1000000 / (8 * asset->edit_rate().as_float())); int const risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float())); - auto check_frame_size = [max_frame, risky_frame, file, start_frame](int index, int size, int frame_rate, vector<VerificationNote>& notes) { + bool any_bad_frames_seen = false; + + auto check_frame_size = [max_frame, risky_frame, file, start_frame, &any_bad_frames_seen](Context& context, int index, int size, int frame_rate) { if (size > max_frame) { - notes.push_back( + context.add_note( VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file ).set_frame(start_frame + index).set_frame_rate(frame_rate) ); + any_bad_frames_seen = true; } else if (size > risky_frame) { - notes.push_back( + context.add_note( VerificationNote( VerificationNote::Type::WARNING, VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, file ).set_frame(start_frame + index).set_frame_rate(frame_rate) ); + any_bad_frames_seen = true; } }; - if (auto mono_asset = dynamic_pointer_cast<MonoPictureAsset>(reel_file_asset->asset_ref().asset())) { + if (auto mono_asset = dynamic_pointer_cast<MonoJ2KPictureAsset>(reel_file_asset->asset_ref().asset())) { auto reader = mono_asset->start_read (); for (int64_t i = 0; i < duration; ++i) { auto frame = reader->get_frame (i); - check_frame_size(i, frame->size(), mono_asset->frame_rate().numerator, notes); + check_frame_size(context, i, frame->size(), mono_asset->frame_rate().numerator); if (!mono_asset->encrypted() || mono_asset->key()) { vector<VerificationNote> j2k_notes; verify_j2k(frame, start_frame, i, mono_asset->frame_rate().numerator, j2k_notes); check_and_add (j2k_notes); } - progress (float(i) / duration); + context.progress(float(i) / duration); } - } else if (auto stereo_asset = dynamic_pointer_cast<StereoPictureAsset>(asset)) { + } else if (auto stereo_asset = dynamic_pointer_cast<StereoJ2KPictureAsset>(asset)) { auto reader = stereo_asset->start_read (); for (int64_t i = 0; i < duration; ++i) { auto frame = reader->get_frame (i); - check_frame_size(i, frame->left()->size(), stereo_asset->frame_rate().numerator, notes); - check_frame_size(i, frame->right()->size(), stereo_asset->frame_rate().numerator, notes); + check_frame_size(context, i, frame->left()->size(), stereo_asset->frame_rate().numerator); + check_frame_size(context, i, frame->right()->size(), stereo_asset->frame_rate().numerator); if (!stereo_asset->encrypted() || stereo_asset->key()) { vector<VerificationNote> j2k_notes; verify_j2k(frame->left(), start_frame, i, stereo_asset->frame_rate().numerator, j2k_notes); verify_j2k(frame->right(), start_frame, i, stereo_asset->frame_rate().numerator, j2k_notes); check_and_add (j2k_notes); } - progress (float(i) / duration); + context.progress(float(i) / duration); } } + + if (!any_bad_frames_seen) { + context.ok(VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES, file); + } } static void -verify_main_picture_asset ( - shared_ptr<const DCP> dcp, - shared_ptr<const ReelPictureAsset> reel_asset, - int64_t start_frame, - function<void (string, optional<boost::filesystem::path>)> stage, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes - ) +verify_main_picture_asset(Context& context, shared_ptr<const ReelPictureAsset> reel_asset, int64_t start_frame) { auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { - stage ("Checking picture asset hash", file); + if (context.options.check_asset_hashes && (!context.options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *context.options.maximum_asset_size_for_hash_check)) { + context.stage("Checking picture asset hash", file); string reference_hash; string calculated_hash; - auto const r = verify_asset(dcp, reel_asset, progress, &reference_hash, &calculated_hash); + auto const r = verify_asset(context, reel_asset, &reference_hash, &calculated_hash); switch (r) { case VerifyAssetResult::BAD: - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_PICTURE_HASH, @@ -542,17 +617,16 @@ verify_main_picture_asset ( ); break; case VerifyAssetResult::CPL_PKL_DIFFER: - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_PICTURE_HASHES, file - }); + context.error(VerificationNote::Code::MISMATCHED_PICTURE_HASHES, file); break; default: + context.ok(VerificationNote::Code::CORRECT_PICTURE_HASH, file); break; } } - stage ("Checking picture frame sizes", asset->file()); - verify_picture_asset(reel_asset, file, start_frame, notes, progress); + context.stage("Checking picture frame sizes", asset->file()); + verify_picture_asset(context, reel_asset, file, start_frame); /* Only flat/scope allowed by Bv2.1 */ if ( @@ -560,12 +634,7 @@ verify_main_picture_asset ( asset->size() != Size(1998, 1080) && asset->size() != Size(4096, 1716) && asset->size() != Size(3996, 2160)) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, - VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS, - String::compose("%1x%2", asset->size().width, asset->size().height), - file - }); + context.bv21_error(VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS, String::compose("%1x%2", asset->size().width, asset->size().height), file); } /* Only 24, 25, 48fps allowed for 2K */ @@ -573,69 +642,50 @@ verify_main_picture_asset ( (asset->size() == Size(2048, 858) || asset->size() == Size(1998, 1080)) && (asset->edit_rate() != Fraction(24, 1) && asset->edit_rate() != Fraction(25, 1) && asset->edit_rate() != Fraction(48, 1)) ) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, + context.bv21_error( VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K, String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), file - }); + ); } if (asset->size() == Size(4096, 1716) || asset->size() == Size(3996, 2160)) { /* Only 24fps allowed for 4K */ if (asset->edit_rate() != Fraction(24, 1)) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, + context.bv21_error( VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K, String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), file - }); + ); } /* Only 2D allowed for 4K */ - if (dynamic_pointer_cast<const StereoPictureAsset>(asset)) { - notes.push_back({ - VerificationNote::Type::BV21_ERROR, + if (dynamic_pointer_cast<const StereoJ2KPictureAsset>(asset)) { + context.bv21_error( VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D, String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator), file - }); + ); } } - } -struct State -{ - boost::optional<string> subtitle_language; - boost::optional<int> audio_channels; -}; - - static void -verify_main_sound_asset ( - shared_ptr<const DCP> dcp, - shared_ptr<const ReelSoundAsset> reel_asset, - function<void (string, optional<boost::filesystem::path>)> stage, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state - ) +verify_main_sound_asset(Context& context, shared_ptr<const ReelSoundAsset> reel_asset) { auto asset = reel_asset->asset(); auto const file = *asset->file(); - if (options.check_asset_hashes && (!options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *options.maximum_asset_size_for_hash_check)) { - stage("Checking sound asset hash", file); + if (context.options.check_asset_hashes && (!context.options.maximum_asset_size_for_hash_check || filesystem::file_size(file) < *context.options.maximum_asset_size_for_hash_check)) { + context.stage("Checking sound asset hash", file); string reference_hash; string calculated_hash; - auto const r = verify_asset(dcp, reel_asset, progress, &reference_hash, &calculated_hash); + auto const r = verify_asset(context, reel_asset, &reference_hash, &calculated_hash); switch (r) { case VerifyAssetResult::BAD: - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_SOUND_HASH, @@ -644,58 +694,61 @@ verify_main_sound_asset ( ); break; case VerifyAssetResult::CPL_PKL_DIFFER: - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_SOUND_HASHES, file}); + context.error(VerificationNote::Code::MISMATCHED_SOUND_HASHES, file); break; default: break; } } - if (!state.audio_channels) { - state.audio_channels = asset->channels(); - } else if (*state.audio_channels != asset->channels()) { - notes.push_back({ VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, file }); + if (!context.audio_channels) { + context.audio_channels = asset->channels(); + } else if (*context.audio_channels != asset->channels()) { + context.error(VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, file); } - stage ("Checking sound asset metadata", file); + context.stage("Checking sound asset metadata", file); if (auto lang = asset->language()) { - verify_language_tag (*lang, notes); + verify_language_tag(context, *lang); } if (asset->sampling_rate() != 48000) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_SOUND_FRAME_RATE, raw_convert<string>(asset->sampling_rate()), file}); + context.bv21_error(VerificationNote::Code::INVALID_SOUND_FRAME_RATE, raw_convert<string>(asset->sampling_rate()), file); + } + if (asset->bit_depth() != 24) { + context.error(VerificationNote::Code::INVALID_SOUND_BIT_DEPTH, raw_convert<string>(asset->bit_depth()), file); } } static void -verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, vector<VerificationNote>& notes) +verify_main_subtitle_reel(Context& context, shared_ptr<const ReelSubtitleAsset> reel_asset) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { - verify_language_tag (*reel_asset->language(), notes); + verify_language_tag(context, *reel_asset->language()); } if (!reel_asset->entry_point()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, reel_asset->id() }); + context.bv21_error(VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT, reel_asset->id()); } else if (reel_asset->entry_point().get()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, reel_asset->id() }); + context.bv21_error(VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT, reel_asset->id()); } } static void -verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset, vector<VerificationNote>& notes) +verify_closed_caption_reel(Context& context, shared_ptr<const ReelClosedCaptionAsset> reel_asset) { /* XXX: is Language compulsory? */ if (reel_asset->language()) { - verify_language_tag (*reel_asset->language(), notes); + verify_language_tag(context, *reel_asset->language()); } if (!reel_asset->entry_point()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id() }); + context.bv21_error(VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id()); } else if (reel_asset->entry_point().get()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id() }); + context.bv21_error(VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT, reel_asset->id()); } } @@ -703,22 +756,20 @@ verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset, /** Verify stuff that is common to both subtitles and closed captions */ void verify_smpte_timed_text_asset ( + Context& context, shared_ptr<const SMPTESubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - vector<VerificationNote>& notes + optional<int64_t> reel_asset_duration ) { if (asset->language()) { - verify_language_tag (*asset->language(), notes); + verify_language_tag(context, *asset->language()); } else { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file() }); + context.bv21_error(VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, *asset->file()); } auto const size = filesystem::file_size(asset->file().get()); if (size > 115 * 1024 * 1024) { - notes.push_back ( - { VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, raw_convert<string>(size), *asset->file() } - ); + context.bv21_error(VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, raw_convert<string>(size), *asset->file()); } /* XXX: I'm not sure what Bv2.1_7.2.1 means when it says "the font resource shall not be larger than 10MB" @@ -730,54 +781,48 @@ verify_smpte_timed_text_asset ( total_size += i.second.size(); } if (total_size > 10 * 1024 * 1024) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, raw_convert<string>(total_size), asset->file().get() }); + context.bv21_error(VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, raw_convert<string>(total_size), asset->file().get()); } if (!asset->start_time()) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_SUBTITLE_START_TIME, asset->file().get() }); + context.bv21_error(VerificationNote::Code::MISSING_SUBTITLE_START_TIME, asset->file().get()); } else if (asset->start_time() != Time()) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_SUBTITLE_START_TIME, asset->file().get() }); + context.bv21_error(VerificationNote::Code::INVALID_SUBTITLE_START_TIME, asset->file().get()); } if (reel_asset_duration && *reel_asset_duration != asset->intrinsic_duration()) { - notes.push_back ( - { - VerificationNote::Type::BV21_ERROR, - VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION, - String::compose("%1 %2", *reel_asset_duration, asset->intrinsic_duration()), - asset->file().get() - }); + context.bv21_error( + VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION, + String::compose("%1 %2", *reel_asset_duration, asset->intrinsic_duration()), + asset->file().get() + ); } } /** Verify Interop subtitle / CCAP stuff */ void -verify_interop_text_asset(shared_ptr<const InteropSubtitleAsset> asset, vector<VerificationNote>& notes) +verify_interop_text_asset(Context& context, shared_ptr<const InteropSubtitleAsset> asset) { if (asset->subtitles().empty()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_SUBTITLE, asset->id(), asset->file().get() }); + context.error(VerificationNote::Code::MISSING_SUBTITLE, asset->id(), asset->file().get()); } auto const unresolved = asset->unresolved_fonts(); if (!unresolved.empty()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_FONT, unresolved.front() }); + context.error(VerificationNote::Code::MISSING_FONT, unresolved.front()); } } /** Verify SMPTE subtitle-only stuff */ void -verify_smpte_subtitle_asset ( - shared_ptr<const SMPTESubtitleAsset> asset, - vector<VerificationNote>& notes, - State& state - ) +verify_smpte_subtitle_asset(Context& context, shared_ptr<const SMPTESubtitleAsset> asset) { if (asset->language()) { - if (!state.subtitle_language) { - state.subtitle_language = *asset->language(); - } else if (state.subtitle_language != *asset->language()) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }); + if (!context.subtitle_language) { + context.subtitle_language = *asset->language(); + } else if (context.subtitle_language != *asset->language()) { + context.bv21_error(VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES); } } @@ -785,14 +830,14 @@ verify_smpte_subtitle_asset ( auto xml_id = asset->xml_id(); if (xml_id) { if (asset->resource_id().get() != xml_id) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID }); + context.bv21_error(VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID); } if (asset->id() == asset->resource_id().get() || asset->id() == xml_id) { - notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID }); + context.bv21_error(VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID); } } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } if (asset->raw_xml()) { @@ -802,7 +847,7 @@ verify_smpte_subtitle_asset ( auto issue_date = doc.string_child("IssueDate"); std::regex reg("^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"); if (!std::regex_match(issue_date, reg)) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, issue_date}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, issue_date); } } } @@ -810,23 +855,16 @@ verify_smpte_subtitle_asset ( /** Verify all subtitle stuff */ static void -verify_subtitle_asset ( - shared_ptr<const SubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes, - State& state - ) +verify_subtitle_asset(Context& context, shared_ptr<const SubtitleAsset> asset, optional<int64_t> reel_asset_duration) { - stage ("Checking subtitle XML", asset->file()); + context.stage("Checking subtitle XML", asset->file()); /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk * gets passed through libdcp which may clean up and therefore hide errors. */ if (asset->raw_xml()) { - validate_xml (asset->raw_xml().get(), xsd_dtd_directory, notes); + validate_xml(context, asset->raw_xml().get()); } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } auto namespace_count = [](shared_ptr<const SubtitleAsset> asset, string root_node) { @@ -842,19 +880,19 @@ verify_subtitle_asset ( auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset); if (interop) { - verify_interop_text_asset(interop, notes); + verify_interop_text_asset(context, interop); if (namespace_count(asset, "DCSubtitle") > 1) { - notes.push_back({ VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }); + context.warning(VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()); } } auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset); if (smpte) { - verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); - verify_smpte_subtitle_asset (smpte, notes, state); + verify_smpte_timed_text_asset(context, smpte, reel_asset_duration); + verify_smpte_subtitle_asset(context, smpte); /* This asset may be encrypted and in that case we'll have no raw_xml() */ if (asset->raw_xml() && namespace_count(asset, "SubtitleReel") > 1) { - notes.push_back({ VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()}); + context.warning(VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id()); } } } @@ -863,35 +901,33 @@ verify_subtitle_asset ( /** Verify all closed caption stuff */ static void verify_closed_caption_asset ( + Context& context, shared_ptr<const SubtitleAsset> asset, - optional<int64_t> reel_asset_duration, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes + optional<int64_t> reel_asset_duration ) { - stage ("Checking closed caption XML", asset->file()); + context.stage("Checking closed caption XML", asset->file()); /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk * gets passed through libdcp which may clean up and therefore hide errors. */ auto raw_xml = asset->raw_xml(); if (raw_xml) { - validate_xml (*raw_xml, xsd_dtd_directory, notes); + validate_xml(context, *raw_xml); if (raw_xml->size() > 256 * 1024) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(raw_xml->size()), *asset->file()}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, raw_convert<string>(raw_xml->size()), *asset->file()); } } else { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); } auto interop = dynamic_pointer_cast<const InteropSubtitleAsset>(asset); if (interop) { - verify_interop_text_asset(interop, notes); + verify_interop_text_asset(context, interop); } auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset); if (smpte) { - verify_smpte_timed_text_asset (smpte, reel_asset_duration, notes); + verify_smpte_timed_text_asset(context, smpte, reel_asset_duration); } } @@ -900,10 +936,9 @@ verify_closed_caption_asset ( static void verify_text_details ( - dcp::Standard standard, + Context& context, vector<shared_ptr<Reel>> reels, int edit_rate, - vector<VerificationNote>& notes, std::function<bool (shared_ptr<Reel>)> check, std::function<optional<string> (shared_ptr<Reel>)> xml, std::function<int64_t (shared_ptr<Reel>)> duration, @@ -997,7 +1032,7 @@ verify_text_details ( auto reel_xml = xml(reels[i]); if (!reel_xml) { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); continue; } @@ -1008,7 +1043,7 @@ verify_text_details ( shared_ptr<cxml::Document> doc; optional<int> tcr; optional<Time> start_time; - switch (standard) { + switch (context.dcp->standard().get_value_or(dcp::Standard::SMPTE)) { case dcp::Standard::INTEROP: doc = make_shared<cxml::Document>("DCSubtitle"); doc->read_string (*reel_xml); @@ -1031,8 +1066,8 @@ verify_text_details ( } reel_offset = end; - if (standard == dcp::Standard::SMPTE && has_text && font_ids.empty()) { - notes.push_back(dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT).set_id(id(reels[i]))); + if (context.dcp->standard() && *context.dcp->standard() == dcp::Standard::SMPTE && has_text && font_ids.empty()) { + context.add_note(dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT).set_id(id(reels[i]))); } } @@ -1041,47 +1076,34 @@ verify_text_details ( } if (too_early) { - notes.push_back({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME); } if (too_short) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_DURATION - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_DURATION); } if (too_close) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_SPACING - }); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_SPACING); } if (reel_overlap) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY - }); + context.error(VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY); } if (empty_text) { - notes.push_back ({ - VerificationNote::Type::WARNING, VerificationNote::Code::EMPTY_TEXT - }); + context.warning(VerificationNote::Code::EMPTY_TEXT); } if (missing_load_font_id) { - notes.push_back(dcp::VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id(*missing_load_font_id)); + context.add_note(dcp::VerificationNote(VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id(*missing_load_font_id)); } } static void -verify_closed_caption_details ( - vector<shared_ptr<Reel>> reels, - vector<VerificationNote>& notes - ) +verify_closed_caption_details(Context& context, vector<shared_ptr<Reel>> reels) { std::function<void (cxml::ConstNodePtr node, std::vector<cxml::ConstNodePtr>& text_or_image)> find_text_or_image; find_text_or_image = [&find_text_or_image](cxml::ConstNodePtr node, std::vector<cxml::ConstNodePtr>& text_or_image) { @@ -1147,7 +1169,7 @@ verify_closed_caption_details ( for (auto ccap: reel->closed_captions()) { auto reel_xml = ccap->asset()->raw_xml(); if (!reel_xml) { - notes.push_back ({VerificationNote::Type::WARNING, VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED}); + context.warning(VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED); continue; } @@ -1170,15 +1192,11 @@ verify_closed_caption_details ( } if (mismatched_valign) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN, - }); + context.error(VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN); } if (incorrect_order) { - notes.push_back ({ - VerificationNote::Type::ERROR, VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING, - }); + context.error(VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING); } } @@ -1284,14 +1302,14 @@ dcp::verify_text_lines_and_characters( static void -verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vector<VerificationNote>& notes) +verify_text_details(Context& context, vector<shared_ptr<Reel>> reels) { if (reels.empty()) { return; } if (reels[0]->main_subtitle() && reels[0]->main_subtitle()->asset_ref().resolved()) { - verify_text_details(standard, reels, reels[0]->main_subtitle()->edit_rate().numerator, notes, + verify_text_details(context, reels, reels[0]->main_subtitle()->edit_rate().numerator, [](shared_ptr<Reel> reel) { return static_cast<bool>(reel->main_subtitle()); }, @@ -1308,7 +1326,7 @@ verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vect } for (auto i = 0U; i < reels[0]->closed_captions().size(); ++i) { - verify_text_details(standard, reels, reels[0]->closed_captions()[i]->edit_rate().numerator, notes, + verify_text_details(context, reels, reels[0]->closed_captions()[i]->edit_rate().numerator, [i](shared_ptr<Reel> reel) { return i < reel->closed_captions().size(); }, @@ -1324,12 +1342,12 @@ verify_text_details(dcp::Standard standard, vector<shared_ptr<Reel>> reels, vect ); } - verify_closed_caption_details (reels, notes); + verify_closed_caption_details(context, reels); } void -verify_extension_metadata(shared_ptr<const CPL> cpl, vector<VerificationNote>& notes) +verify_extension_metadata(Context& context, shared_ptr<const CPL> cpl) { DCP_ASSERT (cpl->file()); cxml::Document doc ("CompositionPlaylist"); @@ -1379,9 +1397,9 @@ verify_extension_metadata(shared_ptr<const CPL> cpl, vector<VerificationNote>& n } if (missing) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->file().get()); } else if (!malformed.empty()) { - notes.push_back ({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_EXTENSION_METADATA, malformed, cpl->file().get()}); + context.bv21_error(VerificationNote::Code::INVALID_EXTENSION_METADATA, malformed, cpl->file().get()); } } @@ -1414,17 +1432,10 @@ pkl_has_encrypted_assets(shared_ptr<const DCP> dcp, shared_ptr<const PKL> pkl) static void verify_reel( - shared_ptr<const DCP> dcp, - shared_ptr<const CPL> cpl, + Context& context, shared_ptr<const Reel> reel, int64_t start_frame, optional<dcp::Size> main_picture_active_area, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state, bool* have_main_subtitle, bool* have_no_main_subtitle, size_t* most_closed_captions, @@ -1434,24 +1445,24 @@ verify_reel( { for (auto i: reel->assets()) { if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_DURATION, i->id()}); + context.error(VerificationNote::Code::INVALID_DURATION, i->id()); } if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_INTRINSIC_DURATION, i->id()}); + context.error(VerificationNote::Code::INVALID_INTRINSIC_DURATION, i->id()); } auto file_asset = dynamic_pointer_cast<ReelFileAsset>(i); if (i->encryptable() && !file_asset->hash()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_HASH, i->id()}); + context.bv21_error(VerificationNote::Code::MISSING_HASH, i->id()); } } - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { boost::optional<int64_t> duration; for (auto i: reel->assets()) { if (!duration) { duration = i->actual_duration(); } else if (*duration != i->actual_duration()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_ASSET_DURATION}); + context.bv21_error(VerificationNote::Code::MISMATCHED_ASSET_DURATION); break; } } @@ -1468,32 +1479,26 @@ verify_reel( frame_rate.numerator != 50 && frame_rate.numerator != 60 && frame_rate.numerator != 96)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, - String::compose("%1/%2", frame_rate.numerator, frame_rate.denominator) - }); + context.error(VerificationNote::Code::INVALID_PICTURE_FRAME_RATE, String::compose("%1/%2", frame_rate.numerator, frame_rate.denominator)); } /* Check asset */ if (reel->main_picture()->asset_ref().resolved()) { - verify_main_picture_asset(dcp, reel->main_picture(), start_frame, stage, progress, options, notes); + verify_main_picture_asset(context, reel->main_picture(), start_frame); auto const asset_size = reel->main_picture()->asset()->size(); if (main_picture_active_area) { if (main_picture_active_area->width > asset_size.width) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("width %1 is bigger than the asset width %2", main_picture_active_area->width, asset_size.width), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("width %1 is bigger than the asset width %2", main_picture_active_area->width, asset_size.width), + context.cpl->file().get() + ); } if (main_picture_active_area->height > asset_size.height) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("height %1 is bigger than the asset height %2", main_picture_active_area->height, asset_size.height), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("height %1 is bigger than the asset height %2", main_picture_active_area->height, asset_size.height), + context.cpl->file().get() + ); } } } @@ -1501,13 +1506,13 @@ verify_reel( } if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) { - verify_main_sound_asset(dcp, reel->main_sound(), stage, progress, options, notes, state); + verify_main_sound_asset(context, reel->main_sound()); } if (reel->main_subtitle()) { - verify_main_subtitle_reel(reel->main_subtitle(), notes); + verify_main_subtitle_reel(context, reel->main_subtitle()); if (reel->main_subtitle()->asset_ref().resolved()) { - verify_subtitle_asset(reel->main_subtitle()->asset(), reel->main_subtitle()->duration(), stage, xsd_dtd_directory, notes, state); + verify_subtitle_asset(context, reel->main_subtitle()->asset(), reel->main_subtitle()->duration()); } *have_main_subtitle = true; } else { @@ -1515,9 +1520,9 @@ verify_reel( } for (auto i: reel->closed_captions()) { - verify_closed_caption_reel(i, notes); + verify_closed_caption_reel(context, i); if (i->asset_ref().resolved()) { - verify_closed_caption_asset(i->asset(), i->duration(), stage, xsd_dtd_directory, notes); + verify_closed_caption_asset(context, i->asset(), i->duration()); } } @@ -1526,10 +1531,10 @@ verify_reel( markers_seen->insert(i); } if (reel->main_markers()->entry_point()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::UNEXPECTED_ENTRY_POINT}); + context.error(VerificationNote::Code::UNEXPECTED_ENTRY_POINT); } if (reel->main_markers()->duration()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::UNEXPECTED_DURATION}); + context.error(VerificationNote::Code::UNEXPECTED_DURATION); } } @@ -1541,26 +1546,21 @@ verify_reel( static void -verify_cpl( - shared_ptr<const DCP> dcp, - shared_ptr<const CPL> cpl, - function<void (string, optional<boost::filesystem::path>)> stage, - boost::filesystem::path xsd_dtd_directory, - function<void (float)> progress, - VerificationOptions options, - vector<VerificationNote>& notes, - State& state - ) +verify_cpl(Context& context, shared_ptr<const CPL> cpl) { - stage("Checking CPL", cpl->file()); - validate_xml(cpl->file().get(), xsd_dtd_directory, notes); + context.stage("Checking CPL", cpl->file()); + validate_xml(context, cpl->file().get()); if (cpl->any_encrypted() && !cpl->all_encrypted()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::PARTIALLY_ENCRYPTED}); + context.bv21_error(VerificationNote::Code::PARTIALLY_ENCRYPTED); + } else if (cpl->all_encrypted()) { + context.ok(VerificationNote::Code::ALL_ENCRYPTED); + } else if (!cpl->all_encrypted()) { + context.ok(VerificationNote::Code::NONE_ENCRYPTED); } for (auto const& i: cpl->additional_subtitle_languages()) { - verify_language_tag(i, notes); + verify_language_tag(context, i); } if (!cpl->content_kind().scope() || *cpl->content_kind().scope() == "http://www.smpte-ra.org/schemas/429-7/2006/CPL#standard-content") { @@ -1572,61 +1572,71 @@ verify_cpl( transform(name.begin(), name.end(), name.begin(), ::tolower); auto iter = std::find_if(all.begin(), all.end(), [name](ContentKind const& k) { return !k.scope() && k.name() == name; }); if (iter == all.end()) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_CONTENT_KIND, cpl->content_kind().name()}); + context.error(VerificationNote::Code::INVALID_CONTENT_KIND, cpl->content_kind().name()); + } else { + context.ok(VerificationNote::Code::VALID_CONTENT_KIND, cpl->content_kind().name()); } } if (cpl->release_territory()) { if (!cpl->release_territory_scope() || cpl->release_territory_scope().get() != "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata#scope/release-territory/UNM49") { auto terr = cpl->release_territory().get(); + bool valid = true; /* Must be a valid region tag, or "001" */ try { LanguageTag::RegionSubtag test(terr); } catch (...) { if (terr != "001") { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_LANGUAGE, terr}); + context.bv21_error(VerificationNote::Code::INVALID_LANGUAGE, terr); + valid = false; } } + if (valid) { + context.ok(VerificationNote::Code::VALID_RELEASE_TERRITORY, terr); + } } } for (auto version: cpl->content_versions()) { if (version.label_text.empty()) { - notes.push_back( - dcp::VerificationNote(VerificationNote::Type::WARNING, VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id()) - ); + context.warning(VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()); break; + } else { + context.ok(VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT, version.label_text); } } - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { if (!cpl->annotation_text()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->file().get()); } else if (cpl->annotation_text().get() != cpl->content_title_text()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), cpl->file().get()}); + context.warning(VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->file().get()); + } else { + context.ok(VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT, cpl->annotation_text().get()); } } - for (auto i: dcp->pkls()) { + for (auto i: context.dcp->pkls()) { /* Check that the CPL's hash corresponds to the PKL */ optional<string> h = i->hash(cpl->id()); auto calculated_cpl_hash = make_digest(ArrayData(*cpl->file())); if (h && calculated_cpl_hash != *h) { - notes.push_back( + context.add_note( dcp::VerificationNote( VerificationNote::Type::ERROR, VerificationNote::Code::MISMATCHED_CPL_HASHES, - cpl->id(), cpl->file().get() ).set_calculated_hash(calculated_cpl_hash).set_reference_hash(*h) ); + } else { + context.ok(VerificationNote::Code::MATCHING_CPL_HASHES); } /* Check that any PKL with a single CPL has its AnnotationText the same as the CPL's ContentTitleText */ optional<string> required_annotation_text; for (auto j: i->assets()) { /* See if this is a CPL */ - for (auto k: dcp->cpls()) { + for (auto k: context.dcp->cpls()) { if (j->id() == k->id()) { if (!required_annotation_text) { /* First CPL we have found; this is the required AnnotationText unless we find another */ @@ -1640,7 +1650,9 @@ verify_cpl( } if (required_annotation_text && i->annotation_text() != required_annotation_text) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, i->id(), i->file().get()}); + context.bv21_error(VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, i->id(), i->file().get()); + } else { + context.ok(VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL); } } @@ -1655,38 +1667,39 @@ verify_cpl( map<Marker, Time> markers_seen; auto const main_picture_active_area = cpl->main_picture_active_area(); + bool active_area_ok = true; if (main_picture_active_area && (main_picture_active_area->width % 2)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("width %1 is not a multiple of 2", main_picture_active_area->width), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("width %1 is not a multiple of 2", main_picture_active_area->width), + cpl->file().get() + ); + active_area_ok = false; } if (main_picture_active_area && (main_picture_active_area->height % 2)) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, - String::compose("height %1 is not a multiple of 2", main_picture_active_area->height), - cpl->file().get() - }); + context.error( + VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, + String::compose("height %1 is not a multiple of 2", main_picture_active_area->height), + cpl->file().get() + ); + active_area_ok = false; + } + + if (main_picture_active_area && active_area_ok) { + context.ok( + VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA, String::compose("%1x%2", main_picture_active_area->width, main_picture_active_area->height), + cpl->file().get() + ); } int64_t frame = 0; for (auto reel: cpl->reels()) { - stage("Checking reel", optional<boost::filesystem::path>()); + context.stage("Checking reel", optional<boost::filesystem::path>()); verify_reel( - dcp, - cpl, + context, reel, frame, main_picture_active_area, - stage, - xsd_dtd_directory, - progress, - options, - notes, - state, &have_main_subtitle, &have_no_main_subtitle, &most_closed_captions, @@ -1696,51 +1709,50 @@ verify_cpl( frame += reel->duration(); } - verify_text_details(dcp->standard().get_value_or(dcp::Standard::SMPTE), cpl->reels(), notes); + verify_text_details(context, cpl->reels()); - if (dcp->standard() == Standard::SMPTE) { + if (context.dcp->standard() == Standard::SMPTE) { if (auto msc = cpl->main_sound_configuration()) { - if (state.audio_channels && msc->channels() != *state.audio_channels) { - notes.push_back({ - VerificationNote::Type::ERROR, - VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, - String::compose("MainSoundConfiguration has %1 channels but sound assets have %2", msc->channels(), *state.audio_channels), - cpl->file().get() - }); + if (context.audio_channels && msc->channels() != *context.audio_channels) { + context.error( + VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, + String::compose("MainSoundConfiguration has %1 channels but sound assets have %2", msc->channels(), *context.audio_channels), + cpl->file().get() + ); } } if (have_main_subtitle && have_no_main_subtitle) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS}); + context.bv21_error(VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS); } if (fewest_closed_captions != most_closed_captions) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS}); + context.bv21_error(VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS); } if (cpl->content_kind() == ContentKind::FEATURE) { if (markers_seen.find(Marker::FFEC) == markers_seen.end()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_FFEC_IN_FEATURE}); + context.bv21_error(VerificationNote::Code::MISSING_FFEC_IN_FEATURE); } if (markers_seen.find(Marker::FFMC) == markers_seen.end()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_FFMC_IN_FEATURE}); + context.bv21_error(VerificationNote::Code::MISSING_FFMC_IN_FEATURE); } } auto ffoc = markers_seen.find(Marker::FFOC); if (ffoc == markers_seen.end()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISSING_FFOC}); + context.warning(VerificationNote::Code::MISSING_FFOC); } else if (ffoc->second.e != 1) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_FFOC, raw_convert<string>(ffoc->second.e)}); + context.warning(VerificationNote::Code::INCORRECT_FFOC, raw_convert<string>(ffoc->second.e)); } auto lfoc = markers_seen.find(Marker::LFOC); if (lfoc == markers_seen.end()) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::MISSING_LFOC}); + context.warning(VerificationNote::Code::MISSING_LFOC); } else { auto lfoc_time = lfoc->second.as_editable_units_ceil(lfoc->second.tcr); if (lfoc_time != (cpl->reels().back()->duration() - 1)) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INCORRECT_LFOC, raw_convert<string>(lfoc_time)}); + context.warning(VerificationNote::Code::INCORRECT_LFOC, raw_convert<string>(lfoc_time)); } } @@ -1752,12 +1764,12 @@ verify_cpl( } if (result.line_count_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT); } if (result.error_length_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH}); + context.warning(VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH); } else if (result.warning_length_exceeded) { - notes.push_back({VerificationNote::Type::WARNING, VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH}); + context.warning(VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH); } result = LinesCharactersResult(); @@ -1770,26 +1782,26 @@ verify_cpl( } if (result.line_count_exceeded) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT); } if (result.error_length_exceeded) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH}); + context.bv21_error(VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH); } if (!cpl->read_composition_metadata()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_METADATA, cpl->file().get()); } else if (!cpl->version_number()) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->file().get()); } - verify_extension_metadata(cpl, notes); + verify_extension_metadata(context, cpl); if (cpl->any_encrypted()) { cxml::Document doc("CompositionPlaylist"); DCP_ASSERT(cpl->file()); doc.read_file(dcp::filesystem::fix_long_path(cpl->file().get())); if (!doc.optional_node_child("Signature")) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), cpl->file().get()}); + context.bv21_error(VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->file().get()); } } } @@ -1798,27 +1810,22 @@ verify_cpl( static void -verify_pkl( - shared_ptr<const DCP> dcp, - shared_ptr<const PKL> pkl, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes - ) +verify_pkl(Context& context, shared_ptr<const PKL> pkl) { - validate_xml(pkl->file().get(), xsd_dtd_directory, notes); + validate_xml(context, pkl->file().get()); - if (pkl_has_encrypted_assets(dcp, pkl)) { + if (pkl_has_encrypted_assets(context.dcp, pkl)) { cxml::Document doc("PackingList"); doc.read_file(dcp::filesystem::fix_long_path(pkl->file().get())); if (!doc.optional_node_child("Signature")) { - notes.push_back({VerificationNote::Type::BV21_ERROR, VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl->id(), pkl->file().get()}); + context.bv21_error(VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl->id(), pkl->file().get()); } } set<string> uuid_set; for (auto asset: pkl->assets()) { if (!uuid_set.insert(asset->id()).second) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl->id(), pkl->file().get()}); + context.error(VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl->id(), pkl->file().get()); break; } } @@ -1828,28 +1835,24 @@ verify_pkl( static void -verify_assetmap( - shared_ptr<const DCP> dcp, - boost::filesystem::path xsd_dtd_directory, - vector<VerificationNote>& notes - ) +verify_assetmap(Context& context, shared_ptr<const DCP> dcp) { auto asset_map = dcp->asset_map(); DCP_ASSERT(asset_map); - validate_xml(asset_map->file().get(), xsd_dtd_directory, notes); + validate_xml(context, asset_map->file().get()); set<string> uuid_set; for (auto const& asset: asset_map->assets()) { if (!uuid_set.insert(asset.id()).second) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map->id(), asset_map->file().get()}); + context.error(VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map->id(), asset_map->file().get()); break; } } } -vector<VerificationNote> +dcp::VerificationResult dcp::verify ( vector<boost::filesystem::path> directories, vector<dcp::DecryptedKDM> kdms, @@ -1865,7 +1868,7 @@ dcp::verify ( *xsd_dtd_directory = filesystem::canonical(*xsd_dtd_directory); vector<VerificationNote> notes; - State state{}; + Context context(notes, *xsd_dtd_directory, stage, progress, options); vector<shared_ptr<DCP>> dcps; for (auto i: directories) { @@ -1874,22 +1877,25 @@ dcp::verify ( for (auto dcp: dcps) { stage ("Checking DCP", dcp->directory()); + + context.dcp = dcp; + bool carry_on = true; try { dcp->read (¬es, true); } catch (MissingAssetmapError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); carry_on = false; } catch (ReadError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (XMLError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (MXFFileError& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (BadURNUUIDError& e) { - notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } catch (cxml::Error& e) { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); + context.error(VerificationNote::Code::FAILED_READ, string(e.what())); } if (!carry_on) { @@ -1906,16 +1912,9 @@ dcp::verify ( for (auto cpl: dcp->cpls()) { try { - verify_cpl( - dcp, - cpl, - stage, - *xsd_dtd_directory, - progress, - options, - notes, - state - ); + context.cpl = cpl; + verify_cpl(context, cpl); + context.cpl.reset(); } catch (ReadError& e) { notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())}); } @@ -1923,23 +1922,23 @@ dcp::verify ( for (auto pkl: dcp->pkls()) { stage("Checking PKL", pkl->file()); - verify_pkl(dcp, pkl, *xsd_dtd_directory, notes); + verify_pkl(context, pkl); } if (dcp->asset_map_file()) { stage("Checking ASSETMAP", dcp->asset_map_file().get()); - verify_assetmap(dcp, *xsd_dtd_directory, notes); + verify_assetmap(context, dcp); } else { - notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::MISSING_ASSETMAP}); + context.error(VerificationNote::Code::MISSING_ASSETMAP); } } - return notes; + return { notes, dcps }; } string -dcp::note_to_string (VerificationNote note) +dcp::note_to_string(VerificationNote note, function<string (string)> process_string, function<string (string)> process_filename) { /** These strings should say what is wrong, incorporating any extra details (ID, filenames etc.). * @@ -1951,238 +1950,269 @@ dcp::note_to_string (VerificationNote note) * End messages with a full stop. * Messages should not mention whether or not their errors are a part of Bv2.1. */ + + auto filename = [note, process_filename]() { + return process_filename(note.file()->filename().string()); + }; + +#define compose(format, ...) String::compose(process_string(format), __VA_ARGS__) + switch (note.code()) { case VerificationNote::Code::FAILED_READ: - return *note.note(); + return process_string(*note.note()); + case VerificationNote::Code::MATCHING_CPL_HASHES: + return process_string("The hash of the CPL in the PKL matches the CPL file."); case VerificationNote::Code::MISMATCHED_CPL_HASHES: - return String::compose("The hash (%1) of the CPL (%2) in the PKL does not agree with the CPL file (%3).", note.reference_hash().get(), note.note().get(), note.calculated_hash().get()); + return compose("The hash (%1) of the CPL (%2) in the PKL does not agree with the CPL file (%3).", note.reference_hash().get(), note.cpl_id().get(), note.calculated_hash().get()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE: - return String::compose("The picture in a reel has an invalid frame rate %1.", note.note().get()); + return compose("The picture in a reel has an invalid frame rate %1.", note.note().get()); case VerificationNote::Code::INCORRECT_PICTURE_HASH: - return String::compose("The hash (%1) of the picture asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), note.file()->filename(), note.reference_hash().get()); + return compose("The hash (%1) of the picture asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), filename(), note.reference_hash().get()); + case VerificationNote::Code::CORRECT_PICTURE_HASH: + return compose("The picture asset %1 has the expected hashes in the CPL and PKL.", filename()); case VerificationNote::Code::MISMATCHED_PICTURE_HASHES: - return String::compose("The PKL and CPL hashes differ for the picture asset %1.", note.file()->filename()); + return compose("The PKL and CPL hashes differ for the picture asset %1.", filename()); case VerificationNote::Code::INCORRECT_SOUND_HASH: - return String::compose("The hash (%1) of the sound asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), note.file()->filename(), note.reference_hash().get()); + return compose("The hash (%1) of the sound asset %2 does not agree with the PKL file (%3).", note.calculated_hash().get(), filename(), note.reference_hash().get()); case VerificationNote::Code::MISMATCHED_SOUND_HASHES: - return String::compose("The PKL and CPL hashes differ for the sound asset %1.", note.file()->filename()); + return compose("The PKL and CPL hashes differ for the sound asset %1.", filename()); case VerificationNote::Code::EMPTY_ASSET_PATH: - return "The asset map contains an empty asset path."; + return process_string("The asset map contains an empty asset path."); case VerificationNote::Code::MISSING_ASSET: - return String::compose("The file %1 for an asset in the asset map cannot be found.", note.file()->filename()); + return compose("The file %1 for an asset in the asset map cannot be found.", filename()); case VerificationNote::Code::MISMATCHED_STANDARD: - return "The DCP contains both SMPTE and Interop parts."; + return process_string("The DCP contains both SMPTE and Interop parts."); case VerificationNote::Code::INVALID_XML: - return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get()); + return compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), filename(), note.line().get()); case VerificationNote::Code::MISSING_ASSETMAP: - return "No valid ASSETMAP or ASSETMAP.xml was found."; + return process_string("No valid ASSETMAP or ASSETMAP.xml was found."); case VerificationNote::Code::INVALID_INTRINSIC_DURATION: - return String::compose("The intrinsic duration of the asset %1 is less than 1 second.", note.note().get()); + return compose("The intrinsic duration of the asset %1 is less than 1 second.", note.note().get()); case VerificationNote::Code::INVALID_DURATION: - return String::compose("The duration of the asset %1 is less than 1 second.", note.note().get()); + return compose("The duration of the asset %1 is less than 1 second.", note.note().get()); + case VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES: + return compose("Each frame of the picture asset %1 has a bit rate safely under the limit of 250Mbit/s.", filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES: - return String::compose( + return compose( "Frame %1 (timecode %2) in asset %3 has an instantaneous bit rate that is larger than the limit of 250Mbit/s.", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), - note.file()->filename() + filename() ); case VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES: - return String::compose( + return compose( "Frame %1 (timecode %2) in asset %3 has an instantaneous bit rate that is close to the limit of 250Mbit/s.", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), - note.file()->filename() + filename() ); case VerificationNote::Code::EXTERNAL_ASSET: - return String::compose("The asset %1 that this DCP refers to is not included in the DCP. It may be a VF.", note.note().get()); + return compose("The asset %1 that this DCP refers to is not included in the DCP. It may be a VF.", note.note().get()); case VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD: - return String::compose("The asset %1 is 3D but its MXF is marked as 2D.", note.file()->filename()); + return compose("The asset %1 is 3D but its MXF is marked as 2D.", filename()); case VerificationNote::Code::INVALID_STANDARD: return "This DCP does not use the SMPTE standard."; case VerificationNote::Code::INVALID_LANGUAGE: - return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get()); + return compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get()); + case VerificationNote::Code::VALID_RELEASE_TERRITORY: + return compose("Valid release territory %1.", note.note().get()); case VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS: - return String::compose("The size %1 of picture asset %2 is not allowed.", note.note().get(), note.file()->filename()); + return compose("The size %1 of picture asset %2 is not allowed.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K: - return String::compose("The frame rate %1 of picture asset %2 is not allowed for 2K DCPs.", note.note().get(), note.file()->filename()); + return compose("The frame rate %1 of picture asset %2 is not allowed for 2K DCPs.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K: - return String::compose("The frame rate %1 of picture asset %2 is not allowed for 4K DCPs.", note.note().get(), note.file()->filename()); + return compose("The frame rate %1 of picture asset %2 is not allowed for 4K DCPs.", note.note().get(), filename()); case VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D: - return "3D 4K DCPs are not allowed."; + return process_string("3D 4K DCPs are not allowed."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES: - return String::compose("The size %1 of the closed caption asset %2 is larger than the 256KB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the closed caption asset %2 is larger than the 256KB maximum.", note.note().get(), filename()); case VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES: - return String::compose("The size %1 of the timed text asset %2 is larger than the 115MB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the timed text asset %2 is larger than the 115MB maximum.", note.note().get(), filename()); case VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES: - return String::compose("The size %1 of the fonts in timed text asset %2 is larger than the 10MB maximum.", note.note().get(), note.file()->filename()); + return compose("The size %1 of the fonts in timed text asset %2 is larger than the 10MB maximum.", note.note().get(), filename()); case VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE: - return String::compose("The XML for the SMPTE subtitle asset %1 has no <Language> tag.", note.file()->filename()); + return compose("The XML for the SMPTE subtitle asset %1 has no <Language> tag.", filename()); case VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES: - return "Some subtitle assets have different <Language> tags than others"; + return process_string("Some subtitle assets have different <Language> tags than others"); case VerificationNote::Code::MISSING_SUBTITLE_START_TIME: - return String::compose("The XML for the SMPTE subtitle asset %1 has no <StartTime> tag.", note.file()->filename()); + return compose("The XML for the SMPTE subtitle asset %1 has no <StartTime> tag.", filename()); case VerificationNote::Code::INVALID_SUBTITLE_START_TIME: - return String::compose("The XML for a SMPTE subtitle asset %1 has a non-zero <StartTime> tag.", note.file()->filename()); + return compose("The XML for a SMPTE subtitle asset %1 has a non-zero <StartTime> tag.", filename()); case VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME: - return "The first subtitle or closed caption is less than 4 seconds from the start of the DCP."; + return process_string("The first subtitle or closed caption is less than 4 seconds from the start of the DCP."); case VerificationNote::Code::INVALID_SUBTITLE_DURATION: - return "At least one subtitle lasts less than 15 frames."; + return process_string("At least one subtitle lasts less than 15 frames."); case VerificationNote::Code::INVALID_SUBTITLE_SPACING: - return "At least one pair of subtitles is separated by less than 2 frames."; + return process_string("At least one pair of subtitles is separated by less than 2 frames."); case VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY: - return "At least one subtitle extends outside of its reel."; + return process_string("At least one subtitle extends outside of its reel."); case VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT: - return "There are more than 3 subtitle lines in at least one place in the DCP."; + return process_string("There are more than 3 subtitle lines in at least one place in the DCP."); case VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH: - return "There are more than 52 characters in at least one subtitle line."; + return process_string("There are more than 52 characters in at least one subtitle line."); case VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH: - return "There are more than 79 characters in at least one subtitle line."; + return process_string("There are more than 79 characters in at least one subtitle line."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT: - return "There are more than 3 closed caption lines in at least one place."; + return process_string("There are more than 3 closed caption lines in at least one place."); case VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH: - return "There are more than 32 characters in at least one closed caption line."; + return process_string("There are more than 32 characters in at least one closed caption line."); case VerificationNote::Code::INVALID_SOUND_FRAME_RATE: - return String::compose("The sound asset %1 has a sampling rate of %2", note.file()->filename(), note.note().get()); + return compose("The sound asset %1 has a sampling rate of %2", filename(), note.note().get()); + case VerificationNote::Code::INVALID_SOUND_BIT_DEPTH: + return compose("The sound asset %1 has a bit depth of %2", filename(), note.note().get()); case VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT: - return String::compose("The CPL %1 has no <AnnotationText> tag.", note.note().get()); + return compose("The CPL %1 has no <AnnotationText> tag.", note.cpl_id().get()); case VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT: - return String::compose("The CPL %1 has an <AnnotationText> which differs from its <ContentTitleText>.", note.note().get()); + return compose("The CPL %1 has an <AnnotationText> which differs from its <ContentTitleText>.", note.cpl_id().get()); + case VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT: + return compose("Valid CPL annotation text %1", note.note().get()); case VerificationNote::Code::MISMATCHED_ASSET_DURATION: - return "All assets in a reel do not have the same duration."; + return process_string("All assets in a reel do not have the same duration."); case VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS: - return "At least one reel contains a subtitle asset, but some reel(s) do not."; + return process_string("At least one reel contains a subtitle asset, but some reel(s) do not."); case VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS: - return "At least one reel has closed captions, but reels have different numbers of closed caption assets."; + return process_string("At least one reel has closed captions, but reels have different numbers of closed caption assets."); case VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT: - return String::compose("The subtitle asset %1 has no <EntryPoint> tag.", note.note().get()); + return compose("The subtitle asset %1 has no <EntryPoint> tag.", note.note().get()); case VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT: - return String::compose("The subtitle asset %1 has an <EntryPoint> other than 0.", note.note().get()); + return compose("The subtitle asset %1 has an <EntryPoint> other than 0.", note.note().get()); case VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT: - return String::compose("The closed caption asset %1 has no <EntryPoint> tag.", note.note().get()); + return compose("The closed caption asset %1 has no <EntryPoint> tag.", note.note().get()); case VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT: - return String::compose("The closed caption asset %1 has an <EntryPoint> other than 0.", note.note().get()); + return compose("The closed caption asset %1 has an <EntryPoint> other than 0.", note.note().get()); case VerificationNote::Code::MISSING_HASH: - return String::compose("The asset %1 has no <Hash> tag in the CPL.", note.note().get()); + return compose("The asset %1 has no <Hash> tag in the CPL.", note.note().get()); case VerificationNote::Code::MISSING_FFEC_IN_FEATURE: - return "The DCP is marked as a Feature but there is no FFEC (first frame of end credits) marker."; + return process_string("The DCP is marked as a Feature but there is no FFEC (first frame of end credits) marker."); case VerificationNote::Code::MISSING_FFMC_IN_FEATURE: - return "The DCP is marked as a Feature but there is no FFMC (first frame of moving credits) marker."; + return process_string("The DCP is marked as a Feature but there is no FFMC (first frame of moving credits) marker."); case VerificationNote::Code::MISSING_FFOC: - return "There should be a FFOC (first frame of content) marker."; + return process_string("There should be a FFOC (first frame of content) marker."); case VerificationNote::Code::MISSING_LFOC: - return "There should be a LFOC (last frame of content) marker."; + return process_string("There should be a LFOC (last frame of content) marker."); case VerificationNote::Code::INCORRECT_FFOC: - return String::compose("The FFOC marker is %1 instead of 1", note.note().get()); + return compose("The FFOC marker is %1 instead of 1", note.note().get()); case VerificationNote::Code::INCORRECT_LFOC: - return String::compose("The LFOC marker is %1 instead of 1 less than the duration of the last reel.", note.note().get()); + return compose("The LFOC marker is %1 instead of 1 less than the duration of the last reel.", note.note().get()); case VerificationNote::Code::MISSING_CPL_METADATA: - return String::compose("The CPL %1 has no <CompositionMetadataAsset> tag.", note.note().get()); + return compose("The CPL %1 has no <CompositionMetadataAsset> tag.", note.cpl_id().get()); case VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER: - return String::compose("The CPL %1 has no <VersionNumber> in its <CompositionMetadataAsset>.", note.note().get()); + return compose("The CPL %1 has no <VersionNumber> in its <CompositionMetadataAsset>.", note.cpl_id().get()); case VerificationNote::Code::MISSING_EXTENSION_METADATA: - return String::compose("The CPL %1 has no <ExtensionMetadata> in its <CompositionMetadataAsset>.", note.note().get()); + return compose("The CPL %1 has no <ExtensionMetadata> in its <CompositionMetadataAsset>.", note.cpl_id().get()); case VerificationNote::Code::INVALID_EXTENSION_METADATA: - return String::compose("The CPL %1 has a malformed <ExtensionMetadata> (%2).", note.file()->filename(), note.note().get()); + return compose("The CPL %1 has a malformed <ExtensionMetadata> (%2).", filename(), note.note().get()); case VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT: - return String::compose("The CPL %1, which has encrypted content, is not signed.", note.note().get()); + return compose("The CPL %1, which has encrypted content, is not signed.", note.cpl_id().get()); case VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT: - return String::compose("The PKL %1, which has encrypted content, is not signed.", note.note().get()); + return compose("The PKL %1, which has encrypted content, is not signed.", note.note().get()); case VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL: - return String::compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>.", note.note().get()); + return compose("The PKL %1 has only one CPL but its <AnnotationText> does not match the CPL's <ContentTitleText>.", note.note().get()); + case VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL: + return process_string("The PKL and CPL annotation texts match."); + case VerificationNote::Code::ALL_ENCRYPTED: + return process_string("All the assets are encrypted."); + case VerificationNote::Code::NONE_ENCRYPTED: + return process_string("All the assets are unencrypted."); case VerificationNote::Code::PARTIALLY_ENCRYPTED: - return "Some assets are encrypted but some are not."; + return process_string("Some assets are encrypted but some are not."); case VerificationNote::Code::INVALID_JPEG2000_CODESTREAM: - return String::compose( + return compose( "Frame %1 (timecode %2) has an invalid JPEG2000 codestream (%3).", note.frame().get(), dcp::Time(note.frame().get(), note.frame_rate().get(), note.frame_rate().get()).as_string(dcp::Standard::SMPTE), note.note().get() ); case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K: - return String::compose("The JPEG2000 codestream uses %1 guard bits in a 2K image instead of 1.", note.note().get()); + return compose("The JPEG2000 codestream uses %1 guard bits in a 2K image instead of 1.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K: - return String::compose("The JPEG2000 codestream uses %1 guard bits in a 4K image instead of 2.", note.note().get()); + return compose("The JPEG2000 codestream uses %1 guard bits in a 4K image instead of 2.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE: - return "The JPEG2000 tile size is not the same as the image size."; + return process_string("The JPEG2000 tile size is not the same as the image size."); case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH: - return String::compose("The JPEG2000 codestream uses a code block width of %1 instead of 32.", note.note().get()); + return compose("The JPEG2000 codestream uses a code block width of %1 instead of 32.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT: - return String::compose("The JPEG2000 codestream uses a code block height of %1 instead of 32.", note.note().get()); + return compose("The JPEG2000 codestream uses a code block height of %1 instead of 32.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K: - return String::compose("%1 POC markers found in 2K JPEG2000 codestream instead of 0.", note.note().get()); + return compose("%1 POC markers found in 2K JPEG2000 codestream instead of 0.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K: - return String::compose("%1 POC markers found in 4K JPEG2000 codestream instead of 1.", note.note().get()); + return compose("%1 POC markers found in 4K JPEG2000 codestream instead of 1.", note.note().get()); case VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER: - return String::compose("Incorrect POC marker content found (%1).", note.note().get()); + return compose("Incorrect POC marker content found (%1).", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION: - return "POC marker found outside main header."; + return process_string("POC marker found outside main header."); case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K: - return String::compose("The JPEG2000 codestream has %1 tile parts in a 2K image instead of 3.", note.note().get()); + return compose("The JPEG2000 codestream has %1 tile parts in a 2K image instead of 3.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K: - return String::compose("The JPEG2000 codestream has %1 tile parts in a 4K image instead of 6.", note.note().get()); + return compose("The JPEG2000 codestream has %1 tile parts in a 4K image instead of 6.", note.note().get()); case VerificationNote::Code::MISSING_JPEG200_TLM_MARKER: - return "No TLM marker was found in a JPEG2000 codestream."; + return process_string("No TLM marker was found in a JPEG2000 codestream."); case VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID: - return "The Resource ID in a timed text MXF did not match the ID of the contained XML."; + return process_string("The Resource ID in a timed text MXF did not match the ID of the contained XML."); case VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID: - return "The Asset ID in a timed text MXF is the same as the Resource ID or that of the contained XML."; + return process_string("The Asset ID in a timed text MXF is the same as the Resource ID or that of the contained XML."); case VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION: { vector<string> parts; boost::split (parts, note.note().get(), boost::is_any_of(" ")); DCP_ASSERT (parts.size() == 2); - return String::compose("The reel duration of some timed text (%1) is not the same as the ContainerDuration of its MXF (%2).", parts[0], parts[1]); + return compose("The reel duration of some timed text (%1) is not the same as the ContainerDuration of its MXF (%2).", parts[0], parts[1]); } case VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED: - return "Some aspect of this DCP could not be checked because it is encrypted."; + return process_string("Some aspect of this DCP could not be checked because it is encrypted."); case VerificationNote::Code::EMPTY_TEXT: - return "There is an empty <Text> node in a subtitle or closed caption."; + return process_string("There is an empty <Text> node in a subtitle or closed caption."); case VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN: - return "Some closed <Text> or <Image> nodes have different vertical alignments within a <Subtitle>."; + return process_string("Some closed <Text> or <Image> nodes have different vertical alignments within a <Subtitle>."); case VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING: - return "Some closed captions are not listed in the order of their vertical position."; + return process_string("Some closed captions are not listed in the order of their vertical position."); case VerificationNote::Code::UNEXPECTED_ENTRY_POINT: - return "There is an <EntryPoint> node inside a <MainMarkers>."; + return process_string("There is an <EntryPoint> node inside a <MainMarkers>."); case VerificationNote::Code::UNEXPECTED_DURATION: - return "There is an <Duration> node inside a <MainMarkers>."; + return process_string("There is an <Duration> node inside a <MainMarkers>."); case VerificationNote::Code::INVALID_CONTENT_KIND: - return String::compose("<ContentKind> has an invalid value %1.", note.note().get()); + return compose("<ContentKind> has an invalid value %1.", note.note().get()); + case VerificationNote::Code::VALID_CONTENT_KIND: + return compose("Valid <ContentKind> %1.", note.note().get()); case VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA: - return String::compose("<MainPictureActiveaArea> has an invalid value: %1", note.note().get()); + return compose("<MainPictureActiveaArea> has an invalid value: %1", note.note().get()); + case VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA: + return compose("<MainPictureActiveaArea> %1 is valid", note.note().get()); case VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL: - return String::compose("The PKL %1 has more than one asset with the same ID.", note.note().get()); + return compose("The PKL %1 has more than one asset with the same ID.", note.note().get()); case VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP: - return String::compose("The ASSETMAP %1 has more than one asset with the same ID.", note.note().get()); + return compose("The ASSETMAP %1 has more than one asset with the same ID.", note.note().get()); case VerificationNote::Code::MISSING_SUBTITLE: - return String::compose("The subtitle asset %1 has no subtitles.", note.note().get()); + return compose("The subtitle asset %1 has no subtitles.", note.note().get()); case VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE: - return String::compose("<IssueDate> has an invalid value: %1", note.note().get()); + return compose("<IssueDate> has an invalid value: %1", note.note().get()); case VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS: - return String::compose("The sound assets do not all have the same channel count; the first to differ is %1", note.file()->filename()); + return compose("The sound assets do not all have the same channel count; the first to differ is %1", filename()); case VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION: - return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get()); + return compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get()); case VerificationNote::Code::MISSING_FONT: - return String::compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get()); + return compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get()); case VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE: - return String::compose( + return compose( "Frame %1 has an image component that is too large (component %2 is %3 bytes in size).", note.frame().get(), note.component().get(), note.size().get() ); case VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT: - return String::compose("The XML in the subtitle asset %1 has more than one namespace declaration.", note.note().get()); + return compose("The XML in the subtitle asset %1 has more than one namespace declaration.", note.note().get()); case VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT: - return String::compose("A subtitle or closed caption refers to a font with ID %1 that does not have a corresponding <LoadFont> node", note.id().get()); + return compose("A subtitle or closed caption refers to a font with ID %1 that does not have a corresponding <LoadFont> node", note.id().get()); case VerificationNote::Code::MISSING_LOAD_FONT: - return String::compose("The SMPTE subtitle asset %1 has <Text> nodes but no <LoadFont> node", note.id().get()); + return compose("The SMPTE subtitle asset %1 has <Text> nodes but no <LoadFont> node", note.id().get()); case VerificationNote::Code::MISMATCHED_ASSET_MAP_ID: - return String::compose("The asset with ID %1 in the asset map actually has an id of %2", note.id().get(), note.other_id().get()); + return compose("The asset with ID %1 in the asset map actually has an id of %2", note.id().get(), note.other_id().get()); case VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT: - return String::compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.id().get()); + return compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.cpl_id().get()); + case VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT: + return compose("CPL has valid <ContentVersion> %1", note.note().get()); case VerificationNote::Code::INVALID_CPL_NAMESPACE: - return String::compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.file()->filename()); + return compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.cpl_id().get()); case VerificationNote::Code::MISSING_CPL_CONTENT_VERSION: - return String::compose("The CPL %1 has no <ContentVersion> tag", note.note().get()); + return compose("The CPL %1 has no <ContentVersion> tag", note.cpl_id().get()); } return ""; @@ -2203,12 +2233,20 @@ dcp::operator== (dcp::VerificationNote const& a, dcp::VerificationNote const& b) a.id() == b.id() && a.other_id() == b.other_id() && a.frame_rate() == b.frame_rate() && + a.cpl_id() == b.cpl_id() && a.reference_hash() == b.reference_hash() && a.calculated_hash() == b.calculated_hash(); } bool +dcp::operator!=(dcp::VerificationNote const& a, dcp::VerificationNote const& b) +{ + return !(a == b); +} + + +bool dcp::operator< (dcp::VerificationNote const& a, dcp::VerificationNote const& b) { if (a.type() != b.type()) { diff --git a/src/verify.h b/src/verify.h index 204d83b0..e9a67f83 100644 --- a/src/verify.h +++ b/src/verify.h @@ -58,6 +58,7 @@ namespace dcp { +class DCP; class SubtitleAsset; @@ -65,12 +66,13 @@ 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" @@ -101,6 +103,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 @@ -112,6 +115,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 @@ -156,6 +160,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 */ @@ -178,6 +183,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 @@ -250,6 +256,11 @@ public: * file contains the asset filename */ INVALID_SOUND_FRAME_RATE, + /** The audio bit depth must be 24 + * note contains the invalid bit depth + * file contains the asset filename + */ + INVALID_SOUND_BIT_DEPTH, /** The CPL has no _<AnnotationText>_ tag [Bv2.1_8.1] * note contains the CPL ID * file contains the CPL filename @@ -260,6 +271,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 */ @@ -337,6 +349,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 @@ -409,11 +426,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 @@ -478,13 +497,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 @@ -545,6 +564,7 @@ private: ID, OTHER_ID, FRAME_RATE, + CPL_ID, CALCULATED_HASH, REFERENCE_HASH }; @@ -644,6 +664,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; @@ -661,18 +690,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_j2k.h b/src/verify_j2k.h index 58c8f4b7..dbfc488b 100644 --- a/src/verify_j2k.h +++ b/src/verify_j2k.h @@ -37,8 +37,8 @@ */ -#ifndef LIBDCP_VERIFY_J2K -#define LIBDCP_VERIFY_J2K +#ifndef LIBDCP_VERIFY_J2K_H +#define LIBDCP_VERIFY_J2K_H #include "verify.h" 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..1793aa72 100644 --- a/src/wscript +++ b/src/wscript @@ -36,14 +36,14 @@ def build(bld): source = """ array_data.cc asset.cc - asset_map.cc asset_factory.cc + asset_map.cc asset_writer.cc atmos_asset.cc atmos_asset_writer.cc bitstream.cc - certificate_chain.cc certificate.cc + certificate_chain.cc chromaticity.cc colour_conversion.cc combine.cc @@ -58,6 +58,7 @@ def build(bld): exceptions.cc file.cc filesystem.cc + ffmpeg_image.cc font_asset.cc fsk.cc gamma_transfer_function.cc @@ -65,6 +66,8 @@ def build(bld): identity_transfer_function.cc interop_load_font_node.cc interop_subtitle_asset.cc + j2k_picture_asset.cc + j2k_picture_asset_writer.cc j2k_transcode.cc key.cc language_tag.cc @@ -72,15 +75,20 @@ def build(bld): locale_convert.cc metadata.cc modified_gamma_transfer_function.cc - mono_picture_asset.cc - mono_picture_asset_writer.cc - mono_picture_frame.cc + mono_j2k_picture_asset.cc + mono_j2k_picture_asset_writer.cc + mono_j2k_picture_frame.cc + mono_mpeg2_picture_asset.cc + mono_mpeg2_picture_asset_writer.cc + mono_mpeg2_picture_frame.cc + mpeg2_picture_asset.cc + mpeg2_picture_asset_writer.cc + mpeg2_transcode.cc mxf.cc name_format.cc object.cc openjpeg_image.cc picture_asset.cc - picture_asset_writer.cc pkl.cc rating.cc raw_convert.cc @@ -91,9 +99,9 @@ def build(bld): reel_file_asset.cc reel_interop_closed_caption_asset.cc reel_interop_subtitle_asset.cc + reel_markers_asset.cc reel_mono_picture_asset.cc reel_picture_asset.cc - reel_markers_asset.cc reel_smpte_closed_caption_asset.cc reel_smpte_subtitle_asset.cc reel_sound_asset.cc @@ -109,9 +117,9 @@ def build(bld): sound_asset.cc sound_asset_writer.cc sound_frame.cc - stereo_picture_asset.cc - stereo_picture_asset_writer.cc - stereo_picture_frame.cc + stereo_j2k_picture_asset.cc + stereo_j2k_picture_asset_writer.cc + stereo_j2k_picture_frame.cc subtitle.cc subtitle_asset.cc subtitle_asset_internal.cc @@ -125,6 +133,7 @@ def build(bld): v_align.cc verify.cc verify_j2k.cc + verify_report.cc version.cc """ @@ -139,8 +148,9 @@ def build(bld): atmos_asset_reader.h atmos_asset_writer.h atmos_frame.h - certificate_chain.h + behaviour.h certificate.h + certificate_chain.h chromaticity.h colour_conversion.h combine.h @@ -161,12 +171,16 @@ def build(bld): filesystem.h font_asset.h frame.h + frame_info.h fsk.h gamma_transfer_function.h h_align.h identity_transfer_function.h interop_load_font_node.h interop_subtitle_asset.h + ffmpeg_image.h + j2k_picture_asset.h + j2k_picture_asset_writer.h j2k_transcode.h key.h language_tag.h @@ -174,22 +188,27 @@ def build(bld): local_time.h locale_convert.h metadata.h - mono_picture_asset.h - mono_picture_asset_reader.h - mono_picture_asset_writer.h - mono_picture_frame.h + mpeg2_picture_asset_writer.h modified_gamma_transfer_function.h + mono_j2k_picture_asset.h + mono_j2k_picture_asset_reader.h + mono_j2k_picture_asset_writer.h + mono_j2k_picture_frame.h + mono_mpeg2_picture_asset.h + mono_mpeg2_picture_asset_reader.h + mono_mpeg2_picture_asset_writer.h + mono_mpeg2_picture_frame.h + mpeg2_picture_asset.h + mpeg2_transcode.h mxf.h name_format.h object.h openjpeg_image.h picture_asset.h - picture_asset_writer.h piecewise_lut.h pkl.h rating.h raw_convert.h - rgb_xyz.h reel.h reel_asset.h reel_atmos_asset.h @@ -200,26 +219,27 @@ def build(bld): reel_markers_asset.h reel_mono_picture_asset.h reel_picture_asset.h - reel_sound_asset.h reel_smpte_closed_caption_asset.h reel_smpte_subtitle_asset.h + reel_sound_asset.h reel_stereo_picture_asset.h reel_subtitle_asset.h ref.h + rgb_xyz.h ruby.h s_gamut3_transfer_function.h scope_guard.h search.h smpte_load_font_node.h smpte_subtitle_asset.h - sound_frame.h sound_asset.h sound_asset_reader.h sound_asset_writer.h - stereo_picture_asset.h - stereo_picture_asset_reader.h - stereo_picture_asset_writer.h - stereo_picture_frame.h + sound_frame.h + stereo_j2k_picture_asset.h + stereo_j2k_picture_asset_reader.h + stereo_j2k_picture_asset_writer.h + stereo_j2k_picture_frame.h subtitle.h subtitle_asset.h subtitle_image.h @@ -232,6 +252,7 @@ def build(bld): v_align.h verify.h verify_j2k.h + verify_report.h version.h warnings.h """ @@ -244,7 +265,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 AVCODEC AVUTIL' obj.source = source # Library for gcov @@ -256,7 +277,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 AVCODEC AVUTIL' 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'] |
