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 stage ("Checking sound asset hash", reel->main_sound()->asset()->file());
536 VerifyAssetResult const r = verify_asset (dcp, reel->main_sound(), progress);
538 case VERIFY_ASSET_RESULT_BAD:
541 VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *reel->main_sound()->asset()->file()
545 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
548 VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE, *reel->main_sound()->asset()->file()
559 verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, list<VerificationNote>& notes)
561 /* XXX: is Language compulsory? */
562 if (reel_asset->language()) {
563 verify_language_tag (*reel_asset->language(), notes);
569 verify_main_subtitle_asset (
570 shared_ptr<const Reel> reel,
571 function<void (string, optional<boost::filesystem::path>)> stage,
572 boost::filesystem::path xsd_dtd_directory,
573 list<VerificationNote>& notes
576 shared_ptr<SubtitleAsset> asset = reel->main_subtitle()->asset();
577 stage ("Checking subtitle XML", asset->file());
578 /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
579 * gets passed through libdcp which may clean up and therefore hide errors.
581 validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
583 shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset>(asset);
585 if (smpte->language()) {
586 verify_language_tag (*smpte->language(), notes);
592 list<VerificationNote>
594 vector<boost::filesystem::path> directories,
595 function<void (string, optional<boost::filesystem::path>)> stage,
596 function<void (float)> progress,
597 boost::filesystem::path xsd_dtd_directory
600 xsd_dtd_directory = boost::filesystem::canonical (xsd_dtd_directory);
602 list<VerificationNote> notes;
604 list<shared_ptr<DCP> > dcps;
605 BOOST_FOREACH (boost::filesystem::path i, directories) {
606 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
609 BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
610 stage ("Checking DCP", dcp->directory());
613 } catch (ReadError& e) {
614 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
615 } catch (XMLError& e) {
616 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
617 } catch (MXFFileError& e) {
618 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
619 } catch (cxml::Error& e) {
620 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
623 if (dcp->standard() != dcp::SMPTE) {
624 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::NOT_SMPTE));
627 BOOST_FOREACH (shared_ptr<CPL> cpl, dcp->cpls()) {
628 stage ("Checking CPL", cpl->file());
629 validate_xml (cpl->file().get(), xsd_dtd_directory, notes);
631 /* Check that the CPL's hash corresponds to the PKL */
632 BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
633 optional<string> h = i->hash(cpl->id());
634 if (h && make_digest(ArrayData(*cpl->file())) != *h) {
635 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
639 BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
640 stage ("Checking reel", optional<boost::filesystem::path>());
642 BOOST_FOREACH (shared_ptr<ReelAsset> i, reel->assets()) {
643 if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
644 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::DURATION_TOO_SMALL, i->id()));
646 if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
647 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INTRINSIC_DURATION_TOO_SMALL, i->id()));
651 if (reel->main_picture()) {
652 /* Check reel stuff */
653 Fraction const frame_rate = reel->main_picture()->frame_rate();
654 if (frame_rate.denominator != 1 ||
655 (frame_rate.numerator != 24 &&
656 frame_rate.numerator != 25 &&
657 frame_rate.numerator != 30 &&
658 frame_rate.numerator != 48 &&
659 frame_rate.numerator != 50 &&
660 frame_rate.numerator != 60 &&
661 frame_rate.numerator != 96)) {
662 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
665 if (reel->main_picture()->asset_ref().resolved()) {
666 verify_main_picture_asset (dcp, reel, stage, progress, notes);
670 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
671 verify_main_sound_asset (dcp, reel, stage, progress, notes);
674 if (reel->main_subtitle()) {
675 verify_main_subtitle_reel (reel->main_subtitle(), notes);
676 if (reel->main_subtitle()->asset_ref().resolved()) {
677 verify_main_subtitle_asset (reel, stage, xsd_dtd_directory, notes);
683 BOOST_FOREACH (shared_ptr<PKL> pkl, dcp->pkls()) {
684 stage ("Checking PKL", pkl->file());
685 validate_xml (pkl->file().get(), xsd_dtd_directory, notes);
688 if (dcp->asset_map_path()) {
689 stage ("Checking ASSETMAP", dcp->asset_map_path().get());
690 validate_xml (dcp->asset_map_path().get(), xsd_dtd_directory, notes);
692 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::MISSING_ASSETMAP));
700 dcp::note_to_string (dcp::VerificationNote note)
702 switch (note.code()) {
703 case dcp::VerificationNote::GENERAL_READ:
705 case dcp::VerificationNote::CPL_HASH_INCORRECT:
706 return "The hash of the CPL in the PKL does not agree with the CPL file.";
707 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
708 return "The picture in a reel has an invalid frame rate.";
709 case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
710 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file.", note.file()->filename());
711 case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
712 return dcp::String::compose("The PKL and CPL hashes disagree for the picture asset %1.", note.file()->filename());
713 case dcp::VerificationNote::SOUND_HASH_INCORRECT:
714 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file.", note.file()->filename());
715 case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
716 return dcp::String::compose("The PKL and CPL hashes disagree for the sound asset %1.", note.file()->filename());
717 case dcp::VerificationNote::EMPTY_ASSET_PATH:
718 return "The asset map contains an empty asset path.";
719 case dcp::VerificationNote::MISSING_ASSET:
720 return String::compose("The file for an asset in the asset map cannot be found; missing file is %1.", note.file()->filename());
721 case dcp::VerificationNote::MISMATCHED_STANDARD:
722 return "The DCP contains both SMPTE and Interop parts.";
723 case dcp::VerificationNote::XML_VALIDATION_ERROR:
724 return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get());
725 case dcp::VerificationNote::MISSING_ASSETMAP:
726 return "No ASSETMAP or ASSETMAP.xml was found.";
727 case dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL:
728 return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get());
729 case dcp::VerificationNote::DURATION_TOO_SMALL:
730 return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get());
731 case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE:
732 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());
733 case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE:
734 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());
735 case dcp::VerificationNote::EXTERNAL_ASSET:
736 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());
737 case dcp::VerificationNote::NOT_SMPTE:
738 return "This DCP does not use the SMPTE standard, which is required for Bv2.1 compliance.";
739 case dcp::VerificationNote::BAD_LANGUAGE:
740 return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get());