2 Copyright (C) 2018-2021 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_closed_caption_asset.h"
39 #include "reel_picture_asset.h"
40 #include "reel_sound_asset.h"
41 #include "reel_subtitle_asset.h"
42 #include "interop_subtitle_asset.h"
43 #include "mono_picture_asset.h"
44 #include "mono_picture_frame.h"
45 #include "stereo_picture_asset.h"
46 #include "stereo_picture_frame.h"
47 #include "exceptions.h"
48 #include "compose.hpp"
49 #include "raw_convert.h"
50 #include "smpte_subtitle_asset.h"
51 #include <xercesc/util/PlatformUtils.hpp>
52 #include <xercesc/parsers/XercesDOMParser.hpp>
53 #include <xercesc/parsers/AbstractDOMParser.hpp>
54 #include <xercesc/sax/HandlerBase.hpp>
55 #include <xercesc/dom/DOMImplementation.hpp>
56 #include <xercesc/dom/DOMImplementationLS.hpp>
57 #include <xercesc/dom/DOMImplementationRegistry.hpp>
58 #include <xercesc/dom/DOMLSParser.hpp>
59 #include <xercesc/dom/DOMException.hpp>
60 #include <xercesc/dom/DOMDocument.hpp>
61 #include <xercesc/dom/DOMNodeList.hpp>
62 #include <xercesc/dom/DOMError.hpp>
63 #include <xercesc/dom/DOMLocator.hpp>
64 #include <xercesc/dom/DOMNamedNodeMap.hpp>
65 #include <xercesc/dom/DOMAttr.hpp>
66 #include <xercesc/dom/DOMErrorHandler.hpp>
67 #include <xercesc/framework/LocalFileInputSource.hpp>
68 #include <xercesc/framework/MemBufInputSource.hpp>
69 #include <boost/noncopyable.hpp>
70 #include <boost/algorithm/string.hpp>
81 using std::shared_ptr;
82 using std::make_shared;
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 auto system_id_str = xml_ch_to_string (system_id);
236 auto 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, 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, vector<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 for (auto 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 for (auto 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 auto const actual_hash = reel_mxf->asset_ref()->hash(progress);
369 auto pkls = dcp->pkls();
370 /* We've read this DCP in so it must have at least one PKL */
371 DCP_ASSERT (!pkls.empty());
373 auto asset = reel_mxf->asset_ref().asset();
375 optional<string> pkl_hash;
377 pkl_hash = i->hash (reel_mxf->asset_ref()->id());
383 DCP_ASSERT (pkl_hash);
385 auto 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, vector<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<const ReelMXF> reel_mxf, function<void (float)> progress)
434 auto asset = dynamic_pointer_cast<A>(reel_mxf->asset_ref().asset());
436 return optional<VerifyPictureAssetResult>();
439 int biggest_frame = 0;
440 auto reader = asset->start_read ();
441 auto 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<const ReelMXF> reel_mxf, function<void (float)> progress)
463 auto 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 ReelPictureAsset> reel_asset,
477 function<void (string, optional<boost::filesystem::path>)> stage,
478 function<void (float)> progress,
479 vector<VerificationNote>& notes
482 auto asset = reel_asset->asset();
483 auto const file = *asset->file();
484 stage ("Checking picture asset hash", file);
485 auto const r = verify_asset (dcp, reel_asset, progress);
487 case VERIFY_ASSET_RESULT_BAD:
490 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file
494 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
497 VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER, file
504 stage ("Checking picture frame sizes", asset->file());
505 auto const pr = verify_picture_asset (reel_asset, progress);
507 case VERIFY_PICTURE_ASSET_RESULT_BAD:
510 VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES, file
514 case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE:
517 VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES, file
525 /* Only flat/scope allowed by Bv2.1 */
527 asset->size() != dcp::Size(2048, 858) &&
528 asset->size() != dcp::Size(1998, 1080) &&
529 asset->size() != dcp::Size(4096, 1716) &&
530 asset->size() != dcp::Size(3996, 2160)) {
533 VerificationNote::VERIFY_BV21_ERROR,
534 VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS,
535 String::compose("%1x%2", asset->size().width, asset->size().height),
541 /* Only 24, 25, 48fps allowed for 2K */
543 (asset->size() == dcp::Size(2048, 858) || asset->size() == dcp::Size(1998, 1080)) &&
544 (asset->edit_rate() != dcp::Fraction(24, 1) && asset->edit_rate() != dcp::Fraction(25, 1) && asset->edit_rate() != dcp::Fraction(48, 1))
548 VerificationNote::VERIFY_BV21_ERROR,
549 VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K,
550 String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator),
556 if (asset->size() == dcp::Size(4096, 1716) || asset->size() == dcp::Size(3996, 2160)) {
557 /* Only 24fps allowed for 4K */
558 if (asset->edit_rate() != dcp::Fraction(24, 1)) {
561 VerificationNote::VERIFY_BV21_ERROR,
562 VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K,
563 String::compose("%1/%2", asset->edit_rate().numerator, asset->edit_rate().denominator),
569 /* Only 2D allowed for 4K */
570 if (dynamic_pointer_cast<const StereoPictureAsset>(asset)) {
573 VerificationNote::VERIFY_BV21_ERROR,
574 VerificationNote::PICTURE_ASSET_4K_3D,
586 verify_main_sound_asset (
587 shared_ptr<const DCP> dcp,
588 shared_ptr<const ReelSoundAsset> reel_asset,
589 function<void (string, optional<boost::filesystem::path>)> stage,
590 function<void (float)> progress,
591 vector<VerificationNote>& notes
594 auto asset = reel_asset->asset();
595 stage ("Checking sound asset hash", asset->file());
596 auto const r = verify_asset (dcp, reel_asset, progress);
598 case VERIFY_ASSET_RESULT_BAD:
601 VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *asset->file()
605 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
608 VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER, *asset->file()
616 stage ("Checking sound asset metadata", asset->file());
618 verify_language_tag (asset->language(), notes);
623 verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, vector<VerificationNote>& notes)
625 /* XXX: is Language compulsory? */
626 if (reel_asset->language()) {
627 verify_language_tag (*reel_asset->language(), notes);
633 verify_closed_caption_reel (shared_ptr<const ReelClosedCaptionAsset> reel_asset, vector<VerificationNote>& notes)
635 /* XXX: is Language compulsory? */
636 if (reel_asset->language()) {
637 verify_language_tag (*reel_asset->language(), notes);
644 boost::optional<string> subtitle_language;
650 verify_smpte_subtitle_asset (
651 shared_ptr<const dcp::SMPTESubtitleAsset> asset,
652 vector<VerificationNote>& notes,
656 if (asset->language()) {
657 auto const language = *asset->language();
658 verify_language_tag (language, notes);
659 if (!state.subtitle_language) {
660 state.subtitle_language = language;
661 } else if (state.subtitle_language != language) {
664 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::SUBTITLE_LANGUAGES_DIFFER, *asset->file()
671 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::MISSING_SUBTITLE_LANGUAGE, *asset->file()
675 if (boost::filesystem::file_size(*asset->file()) > 115 * 1024 * 1024) {
678 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES, *asset->file()
682 /* XXX: I'm not sure what Bv2.1_7.2.1 means when it says "the font resource shall not be larger than 10MB"
683 * but I'm hoping that checking for the total size of all fonts being <= 10MB will do.
685 auto fonts = asset->font_data ();
687 for (auto i: fonts) {
688 total_size += i.second.size();
690 if (total_size > 10 * 1024 * 1024) {
693 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES, *asset->file()
698 if (!asset->start_time()) {
701 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::MISSING_SUBTITLE_START_TIME, *asset->file())
703 } else if (asset->start_time() != dcp::Time()) {
706 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::SUBTITLE_START_TIME_NON_ZERO, *asset->file())
713 verify_subtitle_asset (
714 shared_ptr<const SubtitleAsset> asset,
715 function<void (string, optional<boost::filesystem::path>)> stage,
716 boost::filesystem::path xsd_dtd_directory,
717 vector<VerificationNote>& notes,
721 stage ("Checking subtitle XML", asset->file());
722 /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
723 * gets passed through libdcp which may clean up and therefore hide errors.
725 validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
727 auto smpte = dynamic_pointer_cast<const SMPTESubtitleAsset>(asset);
729 verify_smpte_subtitle_asset (smpte, notes, state);
735 verify_closed_caption_asset (
736 shared_ptr<const SubtitleAsset> asset,
737 function<void (string, optional<boost::filesystem::path>)> stage,
738 boost::filesystem::path xsd_dtd_directory,
739 vector<VerificationNote>& notes,
743 verify_subtitle_asset (asset, stage, xsd_dtd_directory, notes, state);
745 if (asset->raw_xml().size() > 256 * 1024) {
748 VerificationNote::VERIFY_BV21_ERROR, VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES, *asset->file()
758 vector<shared_ptr<dcp::Reel>> reels,
759 optional<int> picture_frame_rate,
760 vector<VerificationNote>& notes,
761 std::function<string (shared_ptr<dcp::Reel>)> xml,
762 std::function<int64_t (shared_ptr<dcp::Reel>)> duration
765 /* end of last subtitle (in editable units) */
766 optional<int64_t> last_out;
767 auto too_short = false;
768 auto too_close = false;
769 auto too_early = false;
770 /* current reel start time (in editable units) */
771 int64_t reel_offset = 0;
773 std::function<void (cxml::ConstNodePtr, int, int, bool)> parse;
774 parse = [&parse, &last_out, &too_short, &too_close, &too_early, &reel_offset](cxml::ConstNodePtr node, int tcr, int pfr, bool first_reel) {
775 if (node->name() == "Subtitle") {
776 dcp::Time in (node->string_attribute("TimeIn"), tcr);
777 dcp::Time out (node->string_attribute("TimeOut"), tcr);
778 if (first_reel && in < dcp::Time(0, 0, 4, 0, tcr)) {
781 auto length = out - in;
782 if (length.as_editable_units(pfr) < 15) {
786 /* XXX: this feels dubious - is it really what Bv2.1 means? */
787 auto distance = reel_offset + in.as_editable_units(pfr) - *last_out;
788 if (distance >= 0 && distance < 2) {
792 last_out = reel_offset + out.as_editable_units(pfr);
794 for (auto i: node->node_children()) {
795 parse(i, tcr, pfr, first_reel);
800 for (auto i = 0U; i < reels.size(); ++i) {
801 /* We need to look at <Subtitle> instances in the XML being checked, so we can't use the subtitles
802 * read in by libdcp's parser.
805 auto doc = make_shared<cxml::Document>("SubtitleReel");
806 doc->read_string (xml(reels[i]));
807 auto const tcr = doc->number_child<int>("TimeCodeRate");
808 parse (doc, tcr, picture_frame_rate.get_value_or(24), i == 0);
809 reel_offset += duration(reels[i]);
815 VerificationNote::VERIFY_WARNING, VerificationNote::FIRST_TEXT_TOO_EARLY
823 VerificationNote::VERIFY_WARNING, VerificationNote::SUBTITLE_TOO_SHORT
831 VerificationNote::VERIFY_WARNING, VerificationNote::SUBTITLE_TOO_CLOSE
840 check_text_timing (vector<shared_ptr<dcp::Reel>> reels, vector<VerificationNote>& notes)
846 optional<int> picture_frame_rate;
847 if (reels[0]->main_picture()) {
848 picture_frame_rate = reels[0]->main_picture()->frame_rate().numerator;
851 if (reels[0]->main_subtitle()) {
852 check_text_timing (reels, picture_frame_rate, notes,
853 [](shared_ptr<dcp::Reel> reel) {
854 return reel->main_subtitle()->asset()->raw_xml();
856 [](shared_ptr<dcp::Reel> reel) {
857 return reel->main_subtitle()->actual_duration();
862 for (auto i = 0U; i < reels[0]->closed_captions().size(); ++i) {
863 check_text_timing (reels, picture_frame_rate, notes,
864 [i](shared_ptr<dcp::Reel> reel) {
865 return reel->closed_captions()[i]->asset()->raw_xml();
867 [i](shared_ptr<dcp::Reel> reel) {
868 return reel->closed_captions()[i]->actual_duration();
875 vector<VerificationNote>
877 vector<boost::filesystem::path> directories,
878 function<void (string, optional<boost::filesystem::path>)> stage,
879 function<void (float)> progress,
880 boost::filesystem::path xsd_dtd_directory
883 xsd_dtd_directory = boost::filesystem::canonical (xsd_dtd_directory);
885 vector<VerificationNote> notes;
888 vector<shared_ptr<DCP>> dcps;
889 for (auto i: directories) {
890 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
893 for (auto dcp: dcps) {
894 stage ("Checking DCP", dcp->directory());
897 } catch (ReadError& e) {
898 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
899 } catch (XMLError& e) {
900 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
901 } catch (MXFFileError& e) {
902 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
903 } catch (cxml::Error& e) {
904 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
907 if (dcp->standard() != dcp::SMPTE) {
908 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::NOT_SMPTE));
911 for (auto cpl: dcp->cpls()) {
912 stage ("Checking CPL", cpl->file());
913 validate_xml (cpl->file().get(), xsd_dtd_directory, notes);
915 for (auto const& i: cpl->additional_subtitle_languages()) {
916 verify_language_tag (i, notes);
919 if (cpl->release_territory()) {
920 verify_language_tag (cpl->release_territory().get(), notes);
923 /* Check that the CPL's hash corresponds to the PKL */
924 for (auto i: dcp->pkls()) {
925 optional<string> h = i->hash(cpl->id());
926 if (h && make_digest(ArrayData(*cpl->file())) != *h) {
927 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
931 for (auto reel: cpl->reels()) {
932 stage ("Checking reel", optional<boost::filesystem::path>());
934 for (auto i: reel->assets()) {
935 if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
936 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::DURATION_TOO_SMALL, i->id()));
938 if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
939 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INTRINSIC_DURATION_TOO_SMALL, i->id()));
943 if (reel->main_picture()) {
944 /* Check reel stuff */
945 auto const frame_rate = reel->main_picture()->frame_rate();
946 if (frame_rate.denominator != 1 ||
947 (frame_rate.numerator != 24 &&
948 frame_rate.numerator != 25 &&
949 frame_rate.numerator != 30 &&
950 frame_rate.numerator != 48 &&
951 frame_rate.numerator != 50 &&
952 frame_rate.numerator != 60 &&
953 frame_rate.numerator != 96)) {
954 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
957 if (reel->main_picture()->asset_ref().resolved()) {
958 verify_main_picture_asset (dcp, reel->main_picture(), stage, progress, notes);
962 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
963 verify_main_sound_asset (dcp, reel->main_sound(), stage, progress, notes);
966 if (reel->main_subtitle()) {
967 verify_main_subtitle_reel (reel->main_subtitle(), notes);
968 if (reel->main_subtitle()->asset_ref().resolved()) {
969 verify_subtitle_asset (reel->main_subtitle()->asset(), stage, xsd_dtd_directory, notes, state);
973 for (auto i: reel->closed_captions()) {
974 verify_closed_caption_reel (i, notes);
975 if (i->asset_ref().resolved()) {
976 verify_closed_caption_asset (i->asset(), stage, xsd_dtd_directory, notes, state);
981 if (dcp->standard() == dcp::SMPTE) {
982 check_text_timing (cpl->reels(), notes);
986 for (auto pkl: dcp->pkls()) {
987 stage ("Checking PKL", pkl->file());
988 validate_xml (pkl->file().get(), xsd_dtd_directory, notes);
991 if (dcp->asset_map_path()) {
992 stage ("Checking ASSETMAP", dcp->asset_map_path().get());
993 validate_xml (dcp->asset_map_path().get(), xsd_dtd_directory, notes);
995 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::MISSING_ASSETMAP));
1003 dcp::note_to_string (dcp::VerificationNote note)
1005 switch (note.code()) {
1006 case dcp::VerificationNote::GENERAL_READ:
1007 return *note.note();
1008 case dcp::VerificationNote::CPL_HASH_INCORRECT:
1009 return "The hash of the CPL in the PKL does not agree with the CPL file.";
1010 case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
1011 return "The picture in a reel has an invalid frame rate.";
1012 case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
1013 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file.", note.file()->filename());
1014 case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER:
1015 return dcp::String::compose("The PKL and CPL hashes differ for the picture asset %1.", note.file()->filename());
1016 case dcp::VerificationNote::SOUND_HASH_INCORRECT:
1017 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file.", note.file()->filename());
1018 case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER:
1019 return dcp::String::compose("The PKL and CPL hashes differ for the sound asset %1.", note.file()->filename());
1020 case dcp::VerificationNote::EMPTY_ASSET_PATH:
1021 return "The asset map contains an empty asset path.";
1022 case dcp::VerificationNote::MISSING_ASSET:
1023 return String::compose("The file for an asset in the asset map cannot be found; missing file is %1.", note.file()->filename());
1024 case dcp::VerificationNote::MISMATCHED_STANDARD:
1025 return "The DCP contains both SMPTE and Interop parts.";
1026 case dcp::VerificationNote::XML_VALIDATION_ERROR:
1027 return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get());
1028 case dcp::VerificationNote::MISSING_ASSETMAP:
1029 return "No ASSETMAP or ASSETMAP.xml was found.";
1030 case dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL:
1031 return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get());
1032 case dcp::VerificationNote::DURATION_TOO_SMALL:
1033 return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get());
1034 case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES:
1035 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());
1036 case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES:
1037 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());
1038 case dcp::VerificationNote::EXTERNAL_ASSET:
1039 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());
1040 case dcp::VerificationNote::NOT_SMPTE:
1041 return "This DCP does not use the SMPTE standard, which is required for Bv2.1 compliance.";
1042 case dcp::VerificationNote::BAD_LANGUAGE:
1043 return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get());
1044 case dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS:
1045 return String::compose("A picture asset's size (%1) is not one of those allowed by Bv2.1 (2048x858, 1998x1080, 4096x1716 or 3996x2160)", note.note().get());
1046 case dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K:
1047 return String::compose("A picture asset's frame rate (%1) is not one of those allowed for 2K DCPs by Bv2.1 (24, 25 or 48fps)", note.note().get());
1048 case dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K:
1049 return String::compose("A picture asset's frame rate (%1) is not 24fps as required for 4K DCPs by Bv2.1", note.note().get());
1050 case dcp::VerificationNote::PICTURE_ASSET_4K_3D:
1051 return "3D 4K DCPs are not allowed by Bv2.1";
1052 case dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES:
1053 return String::compose("The XML for the closed caption asset %1 is longer than the 256KB maximum required by Bv2.1", note.file()->filename());
1054 case dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES:
1055 return String::compose("The total size of the timed text asset %1 is larger than the 115MB maximum required by Bv2.1", note.file()->filename());
1056 case dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES:
1057 return String::compose("The total size of the fonts in timed text asset %1 is larger than the 10MB maximum required by Bv2.1", note.file()->filename());
1058 case dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE:
1059 return String::compose("The XML for a SMPTE subtitle asset has no <Language> tag, which is required by Bv2.1", note.file()->filename());
1060 case dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER:
1061 return String::compose("Some subtitle assets have different <Language> tags than others", note.file()->filename());
1062 case dcp::VerificationNote::MISSING_SUBTITLE_START_TIME:
1063 return String::compose("The XML for a SMPTE subtitle asset has no <StartTime> tag, which is required by Bv2.1", note.file()->filename());
1064 case dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO:
1065 return String::compose("The XML for a SMPTE subtitle asset has a non-zero <StartTime> tag, which is disallowed by Bv2.1", note.file()->filename());
1066 case dcp::VerificationNote::FIRST_TEXT_TOO_EARLY:
1067 return "The first subtitle or closed caption is less than 4 seconds from the start of the DCP.";
1068 case dcp::VerificationNote::SUBTITLE_TOO_SHORT:
1069 return "At least one subtitle is less than the minimum of 15 frames suggested by Bv2.1";
1070 case dcp::VerificationNote::SUBTITLE_TOO_CLOSE:
1071 return "At least one pair of subtitles are separated by less than the the minimum of 2 frames suggested by Bv2.1";