2 Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
38 #include "reel_picture_asset.h"
39 #include "reel_sound_asset.h"
40 #include "reel_subtitle_asset.h"
41 #include "interop_subtitle_asset.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_frame.h"
44 #include "stereo_picture_asset.h"
45 #include "stereo_picture_frame.h"
46 #include "exceptions.h"
47 #include "compose.hpp"
48 #include "raw_convert.h"
49 #include "smpte_subtitle_asset.h"
50 #include <xercesc/util/PlatformUtils.hpp>
51 #include <xercesc/parsers/XercesDOMParser.hpp>
52 #include <xercesc/parsers/AbstractDOMParser.hpp>
53 #include <xercesc/sax/HandlerBase.hpp>
54 #include <xercesc/dom/DOMImplementation.hpp>
55 #include <xercesc/dom/DOMImplementationLS.hpp>
56 #include <xercesc/dom/DOMImplementationRegistry.hpp>
57 #include <xercesc/dom/DOMLSParser.hpp>
58 #include <xercesc/dom/DOMException.hpp>
59 #include <xercesc/dom/DOMDocument.hpp>
60 #include <xercesc/dom/DOMNodeList.hpp>
61 #include <xercesc/dom/DOMError.hpp>
62 #include <xercesc/dom/DOMLocator.hpp>
63 #include <xercesc/dom/DOMNamedNodeMap.hpp>
64 #include <xercesc/dom/DOMAttr.hpp>
65 #include <xercesc/dom/DOMErrorHandler.hpp>
66 #include <xercesc/framework/LocalFileInputSource.hpp>
67 #include <xercesc/framework/MemBufInputSource.hpp>
68 #include <boost/noncopyable.hpp>
69 #include <boost/foreach.hpp>
70 #include <boost/algorithm/string.hpp>
82 using std::shared_ptr;
83 using boost::optional;
84 using boost::function;
85 using std::dynamic_pointer_cast;
88 using namespace xercesc;
92 xml_ch_to_string (XMLCh const * a)
94 char* x = XMLString::transcode(a);
96 XMLString::release(&x);
100 class XMLValidationError
103 XMLValidationError (SAXParseException const & e)
104 : _message (xml_ch_to_string(e.getMessage()))
105 , _line (e.getLineNumber())
106 , _column (e.getColumnNumber())
107 , _public_id (e.getPublicId() ? xml_ch_to_string(e.getPublicId()) : "")
108 , _system_id (e.getSystemId() ? xml_ch_to_string(e.getSystemId()) : "")
113 string message () const {
117 uint64_t line () const {
121 uint64_t column () const {
125 string public_id () const {
129 string system_id () const {
142 class DCPErrorHandler : public ErrorHandler
145 void warning(const SAXParseException& e)
147 maybe_add (XMLValidationError(e));
150 void error(const SAXParseException& e)
152 maybe_add (XMLValidationError(e));
155 void fatalError(const SAXParseException& e)
157 maybe_add (XMLValidationError(e));
164 list<XMLValidationError> errors () const {
169 void maybe_add (XMLValidationError e)
171 /* XXX: nasty hack */
173 e.message().find("schema document") != string::npos &&
174 e.message().find("has different target namespace from the one specified in instance document") != string::npos
179 _errors.push_back (e);
182 list<XMLValidationError> _errors;
185 class StringToXMLCh : public boost::noncopyable
188 StringToXMLCh (string a)
190 _buffer = XMLString::transcode(a.c_str());
195 XMLString::release (&_buffer);
198 XMLCh const * get () const {
206 class LocalFileResolver : public EntityResolver
209 LocalFileResolver (boost::filesystem::path xsd_dtd_directory)
210 : _xsd_dtd_directory (xsd_dtd_directory)
212 /* XXX: I'm not clear on what things need to be in this list; some XSDs are apparently, magically
213 * found without being here.
215 add("http://www.w3.org/2001/XMLSchema.dtd", "XMLSchema.dtd");
216 add("http://www.w3.org/2001/03/xml.xsd", "xml.xsd");
217 add("http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd", "xmldsig-core-schema.xsd");
218 add("http://www.digicine.com/schemas/437-Y/2007/Main-Stereo-Picture-CPL.xsd", "Main-Stereo-Picture-CPL.xsd");
219 add("http://www.digicine.com/PROTO-ASDCP-CPL-20040511.xsd", "PROTO-ASDCP-CPL-20040511.xsd");
220 add("http://www.digicine.com/PROTO-ASDCP-PKL-20040311.xsd", "PROTO-ASDCP-PKL-20040311.xsd");
221 add("http://www.digicine.com/PROTO-ASDCP-AM-20040311.xsd", "PROTO-ASDCP-AM-20040311.xsd");
222 add("http://www.digicine.com/PROTO-ASDCP-CC-CPL-20070926#", "PROTO-ASDCP-CC-CPL-20070926.xsd");
223 add("interop-subs", "DCSubtitle.v1.mattsson.xsd");
224 add("http://www.smpte-ra.org/schemas/428-7/2010/DCST.xsd", "SMPTE-428-7-2010-DCST.xsd");
225 add("http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata", "SMPTE-429-16.xsd");
226 add("http://www.dolby.com/schemas/2012/AD", "Dolby-2012-AD.xsd");
227 add("http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL", "SMPTE-429-10-2008.xsd");
230 InputSource* resolveEntity(XMLCh const *, XMLCh const * system_id)
235 string system_id_str = xml_ch_to_string (system_id);
236 boost::filesystem::path p = _xsd_dtd_directory;
237 if (_files.find(system_id_str) == _files.end()) {
240 p /= _files[system_id_str];
242 StringToXMLCh ch (p.string());
243 return new LocalFileInputSource(ch.get());
247 void add (string uri, string file)
252 std::map<string, string> _files;
253 boost::filesystem::path _xsd_dtd_directory;
258 parse (XercesDOMParser& parser, boost::filesystem::path xml)
260 parser.parse(xml.string().c_str());
265 parse (XercesDOMParser& parser, std::string xml)
267 xercesc::MemBufInputSource buf(reinterpret_cast<unsigned char const*>(xml.c_str()), xml.size(), "");
274 validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, list<VerificationNote>& notes)
277 XMLPlatformUtils::Initialize ();
278 } catch (XMLException& e) {
279 throw MiscError ("Failed to initialise xerces library");
282 DCPErrorHandler error_handler;
284 /* All the xerces objects in this scope must be destroyed before XMLPlatformUtils::Terminate() is called */
286 XercesDOMParser parser;
287 parser.setValidationScheme(XercesDOMParser::Val_Always);
288 parser.setDoNamespaces(true);
289 parser.setDoSchema(true);
291 vector<string> schema;
292 schema.push_back("xml.xsd");
293 schema.push_back("xmldsig-core-schema.xsd");
294 schema.push_back("SMPTE-429-7-2006-CPL.xsd");
295 schema.push_back("SMPTE-429-8-2006-PKL.xsd");
296 schema.push_back("SMPTE-429-9-2007-AM.xsd");
297 schema.push_back("Main-Stereo-Picture-CPL.xsd");
298 schema.push_back("PROTO-ASDCP-CPL-20040511.xsd");
299 schema.push_back("PROTO-ASDCP-PKL-20040311.xsd");
300 schema.push_back("PROTO-ASDCP-AM-20040311.xsd");
301 schema.push_back("DCSubtitle.v1.mattsson.xsd");
302 schema.push_back("DCDMSubtitle-2010.xsd");
303 schema.push_back("PROTO-ASDCP-CC-CPL-20070926.xsd");
304 schema.push_back("SMPTE-429-16.xsd");
305 schema.push_back("Dolby-2012-AD.xsd");
306 schema.push_back("SMPTE-429-10-2008.xsd");
307 schema.push_back("xlink.xsd");
308 schema.push_back("SMPTE-335-2012.xsd");
309 schema.push_back("SMPTE-395-2014-13-1-aaf.xsd");
310 schema.push_back("isdcf-mca.xsd");
311 schema.push_back("SMPTE-429-12-2008.xsd");
313 /* XXX: I'm not especially clear what this is for, but it seems to be necessary.
314 * Schemas that are not mentioned in this list are not read, and the things
315 * they describe are not checked.
318 BOOST_FOREACH (string i, schema) {
319 locations += String::compose("%1 %1 ", i, i);
322 parser.setExternalSchemaLocation(locations.c_str());
323 parser.setValidationSchemaFullChecking(true);
324 parser.setErrorHandler(&error_handler);
326 LocalFileResolver resolver (xsd_dtd_directory);
327 parser.setEntityResolver(&resolver);
330 parser.resetDocumentPool();
332 } catch (XMLException& e) {
333 throw MiscError(xml_ch_to_string(e.getMessage()));
334 } catch (DOMException& e) {
335 throw MiscError(xml_ch_to_string(e.getMessage()));
337 throw MiscError("Unknown exception from xerces");
341 XMLPlatformUtils::Terminate ();
343 BOOST_FOREACH (XMLValidationError i, error_handler.errors()) {
346 VerificationNote::VERIFY_ERROR,
347 VerificationNote::XML_VALIDATION_ERROR,
349 boost::trim_copy(i.public_id() + " " + i.system_id()),
357 enum VerifyAssetResult {
358 VERIFY_ASSET_RESULT_GOOD,
359 VERIFY_ASSET_RESULT_CPL_PKL_DIFFER,
360 VERIFY_ASSET_RESULT_BAD
364 static VerifyAssetResult
365 verify_asset (shared_ptr<const DCP> dcp, shared_ptr<const ReelMXF> reel_mxf, function<void (float)> progress)
367 string const actual_hash = reel_mxf->asset_ref()->hash(progress);
369 list<shared_ptr<PKL> > pkls = dcp->pkls();
370 /* We've read this DCP in so it must have at least one PKL */
371 DCP_ASSERT (!pkls.empty());
373 shared_ptr<Asset> asset = reel_mxf->asset_ref().asset();
375 optional<string> pkl_hash;
376 BOOST_FOREACH (shared_ptr<PKL> i, pkls) {
377 pkl_hash = i->hash (reel_mxf->asset_ref()->id());
383 DCP_ASSERT (pkl_hash);
385 optional<string> cpl_hash = reel_mxf->hash();
386 if (cpl_hash && *cpl_hash != *pkl_hash) {
387 return VERIFY_ASSET_RESULT_CPL_PKL_DIFFER;
390 if (actual_hash != *pkl_hash) {
391 return VERIFY_ASSET_RESULT_BAD;
394 return VERIFY_ASSET_RESULT_GOOD;
399 verify_language_tag (string tag, list<VerificationNote>& notes)
402 dcp::LanguageTag test (tag);
403 } catch (dcp::LanguageTagError &) {
404 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::BAD_LANGUAGE, tag));
409 enum VerifyPictureAssetResult
411 VERIFY_PICTURE_ASSET_RESULT_GOOD,
412 VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE,
413 VERIFY_PICTURE_ASSET_RESULT_BAD,
418 biggest_frame_size (shared_ptr<const MonoPictureFrame> frame)
420 return frame->size ();
424 biggest_frame_size (shared_ptr<const StereoPictureFrame> frame)
426 return max(frame->left()->size(), frame->right()->size());
430 template <class A, class R, class F>
431 optional<VerifyPictureAssetResult>
432 verify_picture_asset_type (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
434 shared_ptr<A> asset = dynamic_pointer_cast<A>(reel_mxf->asset_ref().asset());
436 return optional<VerifyPictureAssetResult>();
439 int biggest_frame = 0;
440 shared_ptr<R> reader = asset->start_read ();
441 int64_t const duration = asset->intrinsic_duration ();
442 for (int64_t i = 0; i < duration; ++i) {
443 shared_ptr<const F> frame = reader->get_frame (i);
444 biggest_frame = max(biggest_frame, biggest_frame_size(frame));
445 progress (float(i) / duration);
448 static const int max_frame = rint(250 * 1000000 / (8 * asset->edit_rate().as_float()));
449 static const int risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float()));
450 if (biggest_frame > max_frame) {
451 return VERIFY_PICTURE_ASSET_RESULT_BAD;
452 } else if (biggest_frame > risky_frame) {
453 return VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE;
456 return VERIFY_PICTURE_ASSET_RESULT_GOOD;
460 static VerifyPictureAssetResult
461 verify_picture_asset (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
463 optional<VerifyPictureAssetResult> r = verify_picture_asset_type<MonoPictureAsset, MonoPictureAssetReader, MonoPictureFrame>(reel_mxf, progress);
465 r = verify_picture_asset_type<StereoPictureAsset, StereoPictureAssetReader, StereoPictureFrame>(reel_mxf, progress);
474 verify_main_picture_asset (
475 shared_ptr<const DCP> dcp,
476 shared_ptr<const Reel> reel,
477 function<void (string, optional<boost::filesystem::path>)> stage,
478 function<void (float)> progress,
479 list<VerificationNote>& notes
482 boost::filesystem::path const file = *reel->main_picture()->asset()->file();
483 stage ("Checking picture asset hash", file);
484 VerifyAssetResult const r = verify_asset (dcp, reel->main_picture(), progress);
486 case VERIFY_ASSET_RESULT_BAD:
489 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file
493 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
496 VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file
503 stage ("Checking picture frame sizes", reel->main_picture()->asset()->file());
504 VerifyPictureAssetResult const pr = verify_picture_asset (reel->main_picture(), progress);
506 case VERIFY_PICTURE_ASSET_RESULT_BAD:
509 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE, file
513 case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE:
516 VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE, file
527 verify_main_sound_asset (
528 shared_ptr<const DCP> dcp,
529 shared_ptr<const Reel> reel,
530 function<void (string, optional<boost::filesystem::path>)> stage,
531 function<void (float)> progress,
532 list<VerificationNote>& notes
535 shared_ptr<dcp::SoundAsset> asset = reel->main_sound()->asset();
536 stage ("Checking sound asset hash", asset->file());
537 VerifyAssetResult const r = verify_asset (dcp, reel->main_sound(), progress);
539 case VERIFY_ASSET_RESULT_BAD:
542 VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *asset->file()
546 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
549 VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE, *asset->file()
557 stage ("Checking sound asset metadata", asset->file());
559 verify_language_tag (asset->language(), notes);
564 verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, list<VerificationNote>& notes)
566 /* XXX: is Language compulsory? */
567 if (reel_asset->language()) {
568 verify_language_tag (*reel_asset->language(), notes);
574 verify_main_subtitle_asset (
575 shared_ptr<const Reel> reel,
576 function<void (string, optional<boost::filesystem::path>)> stage,
577 boost::filesystem::path xsd_dtd_directory,
578 list<VerificationNote>& notes
581 shared_ptr<SubtitleAsset> asset = reel->main_subtitle()->asset();
582 stage ("Checking subtitle XML", asset->file());
583 /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
584 * gets passed through libdcp which may clean up and therefore hide errors.
586 validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
588 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset>(asset);
590 if (smpte->language()) {
591 verify_language_tag (*smpte->language(), notes);
597 list<VerificationNote>
599 vector<boost::filesystem::path> directories,
600 function<void (string, optional<boost::filesystem::path>)> stage,
601 function<void (float)> progress,
602 boost::filesystem::path xsd_dtd_directory
605 xsd_dtd_directory = boost::filesystem::canonical (xsd_dtd_directory);
607 list<VerificationNote> notes;
609 list<shared_ptr<DCP> > dcps;
610 BOOST_FOREACH (boost::filesystem::path i, directories) {
611 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
614 BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
615 stage ("Checking DCP", dcp->directory());
618 } catch (ReadError& e) {
619 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
620 } catch (XMLError& e) {
621 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
622 } catch (MXFFileError& e) {
623 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
624 } catch (cxml::Error& e) {
625 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
628 if (dcp->standard() != dcp::SMPTE) {
629 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::NOT_SMPTE));
632 BOOST_FOREACH (shared_ptr<CPL> cpl, dcp->cpls()) {
633 stage ("Checking CPL", cpl->file());
634 validate_xml (cpl->file().get(), xsd_dtd_directory, notes);
636 /* Check that the CPL's hash corresponds to the PKL */
637 BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
638 optional<string> h = i->hash(cpl->id());
639 if (h && make_digest(ArrayData(*cpl->file())) != *h) {
640 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
644 BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
645 stage ("Checking reel", optional<boost::filesystem::path>());
647 BOOST_FOREACH (shared_ptr<ReelAsset> i, reel->assets()) {
648 if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
649 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::DURATION_TOO_SMALL, i->id()));
651 if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
652 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INTRINSIC_DURATION_TOO_SMALL, i->id()));
656 if (reel->main_picture()) {
657 /* Check reel stuff */
658 Fraction const frame_rate = reel->main_picture()->frame_rate();
659 if (frame_rate.denominator != 1 ||
660 (frame_rate.numerator != 24 &&
661 frame_rate.numerator != 25 &&
662 frame_rate.numerator != 30 &&
663 frame_rate.numerator != 48 &&
664 frame_rate.numerator != 50 &&
665 frame_rate.numerator != 60 &&
666 frame_rate.numerator != 96)) {
667 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
670 if (reel->main_picture()->asset_ref().resolved()) {
671 verify_main_picture_asset (dcp, reel, stage, progress, notes);
675 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
676 verify_main_sound_asset (dcp, reel, stage, progress, notes);
679 if (reel->main_subtitle()) {
680 verify_main_subtitle_reel (reel->main_subtitle(), notes);
681 if (reel->main_subtitle()->asset_ref().resolved()) {
682 verify_main_subtitle_asset (reel, stage, xsd_dtd_directory, notes);
688 BOOST_FOREACH (shared_ptr<PKL> pkl, dcp->pkls()) {
689 stage ("Checking PKL", pkl->file());
690 validate_xml (pkl->file().get(), xsd_dtd_directory, notes);
693 if (dcp->asset_map_path()) {
694 stage ("Checking ASSETMAP", dcp->asset_map_path().get());
695 validate_xml (dcp->asset_map_path().get(), xsd_dtd_directory, notes);
697 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::MISSING_ASSETMAP));
705 dcp::note_to_string (dcp::VerificationNote note)
707 switch (note.code()) {
708 case dcp::VerificationNote::GENERAL_READ:
710 case dcp::VerificationNote::CPL_HASH_INCORRECT:
711 return "The hash of the CPL in the PKL does not agree with the CPL file.";
712 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
713 return "The picture in a reel has an invalid frame rate.";
714 case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
715 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file.", note.file()->filename());
716 case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
717 return dcp::String::compose("The PKL and CPL hashes disagree for the picture asset %1.", note.file()->filename());
718 case dcp::VerificationNote::SOUND_HASH_INCORRECT:
719 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file.", note.file()->filename());
720 case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
721 return dcp::String::compose("The PKL and CPL hashes disagree for the sound asset %1.", note.file()->filename());
722 case dcp::VerificationNote::EMPTY_ASSET_PATH:
723 return "The asset map contains an empty asset path.";
724 case dcp::VerificationNote::MISSING_ASSET:
725 return String::compose("The file for an asset in the asset map cannot be found; missing file is %1.", note.file()->filename());
726 case dcp::VerificationNote::MISMATCHED_STANDARD:
727 return "The DCP contains both SMPTE and Interop parts.";
728 case dcp::VerificationNote::XML_VALIDATION_ERROR:
729 return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get());
730 case dcp::VerificationNote::MISSING_ASSETMAP:
731 return "No ASSETMAP or ASSETMAP.xml was found.";
732 case dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL:
733 return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get());
734 case dcp::VerificationNote::DURATION_TOO_SMALL:
735 return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get());
736 case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE:
737 return String::compose("The instantaneous bit rate of the picture asset %1 is larger than the limit of 250Mbit/s in at least one place.", note.file()->filename());
738 case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE:
739 return String::compose("The instantaneous bit rate of the picture asset %1 is close to the limit of 250Mbit/s in at least one place.", note.file()->filename());
740 case dcp::VerificationNote::EXTERNAL_ASSET:
741 return String::compose("An asset that this DCP refers to is not included in the DCP. It may be a VF. Missing asset is %1.", note.note().get());
742 case dcp::VerificationNote::NOT_SMPTE:
743 return "This DCP does not use the SMPTE standard, which is required for Bv2.1 compliance.";
744 case dcp::VerificationNote::BAD_LANGUAGE:
745 return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get());