Bv2.1 6.2.1: Check that the sound MXF Language tag conforms to RFC 5646.
[libdcp.git] / src / verify.cc
1 /*
2     Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 #include "verify.h"
35 #include "dcp.h"
36 #include "cpl.h"
37 #include "reel.h"
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>
71 #include <map>
72 #include <list>
73 #include <vector>
74 #include <iostream>
75
76 using std::list;
77 using std::vector;
78 using std::string;
79 using std::cout;
80 using std::map;
81 using std::max;
82 using std::shared_ptr;
83 using boost::optional;
84 using boost::function;
85 using std::dynamic_pointer_cast;
86
87 using namespace dcp;
88 using namespace xercesc;
89
90 static
91 string
92 xml_ch_to_string (XMLCh const * a)
93 {
94         char* x = XMLString::transcode(a);
95         string const o(x);
96         XMLString::release(&x);
97         return o;
98 }
99
100 class XMLValidationError
101 {
102 public:
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()) : "")
109         {
110
111         }
112
113         string message () const {
114                 return _message;
115         }
116
117         uint64_t line () const {
118                 return _line;
119         }
120
121         uint64_t column () const {
122                 return _column;
123         }
124
125         string public_id () const {
126                 return _public_id;
127         }
128
129         string system_id () const {
130                 return _system_id;
131         }
132
133 private:
134         string _message;
135         uint64_t _line;
136         uint64_t _column;
137         string _public_id;
138         string _system_id;
139 };
140
141
142 class DCPErrorHandler : public ErrorHandler
143 {
144 public:
145         void warning(const SAXParseException& e)
146         {
147                 maybe_add (XMLValidationError(e));
148         }
149
150         void error(const SAXParseException& e)
151         {
152                 maybe_add (XMLValidationError(e));
153         }
154
155         void fatalError(const SAXParseException& e)
156         {
157                 maybe_add (XMLValidationError(e));
158         }
159
160         void resetErrors() {
161                 _errors.clear ();
162         }
163
164         list<XMLValidationError> errors () const {
165                 return _errors;
166         }
167
168 private:
169         void maybe_add (XMLValidationError e)
170         {
171                 /* XXX: nasty hack */
172                 if (
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
175                         ) {
176                         return;
177                 }
178
179                 _errors.push_back (e);
180         }
181
182         list<XMLValidationError> _errors;
183 };
184
185 class StringToXMLCh : public boost::noncopyable
186 {
187 public:
188         StringToXMLCh (string a)
189         {
190                 _buffer = XMLString::transcode(a.c_str());
191         }
192
193         ~StringToXMLCh ()
194         {
195                 XMLString::release (&_buffer);
196         }
197
198         XMLCh const * get () const {
199                 return _buffer;
200         }
201
202 private:
203         XMLCh* _buffer;
204 };
205
206 class LocalFileResolver : public EntityResolver
207 {
208 public:
209         LocalFileResolver (boost::filesystem::path xsd_dtd_directory)
210                 : _xsd_dtd_directory (xsd_dtd_directory)
211         {
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.
214                  */
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");
228         }
229
230         InputSource* resolveEntity(XMLCh const *, XMLCh const * system_id)
231         {
232                 if (!system_id) {
233                         return 0;
234                 }
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()) {
238                         p /= system_id_str;
239                 } else {
240                         p /= _files[system_id_str];
241                 }
242                 StringToXMLCh ch (p.string());
243                 return new LocalFileInputSource(ch.get());
244         }
245
246 private:
247         void add (string uri, string file)
248         {
249                 _files[uri] = file;
250         }
251
252         std::map<string, string> _files;
253         boost::filesystem::path _xsd_dtd_directory;
254 };
255
256
257 static void
258 parse (XercesDOMParser& parser, boost::filesystem::path xml)
259 {
260         parser.parse(xml.string().c_str());
261 }
262
263
264 static void
265 parse (XercesDOMParser& parser, std::string xml)
266 {
267         xercesc::MemBufInputSource buf(reinterpret_cast<unsigned char const*>(xml.c_str()), xml.size(), "");
268         parser.parse(buf);
269 }
270
271
272 template <class T>
273 void
274 validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, list<VerificationNote>& notes)
275 {
276         try {
277                 XMLPlatformUtils::Initialize ();
278         } catch (XMLException& e) {
279                 throw MiscError ("Failed to initialise xerces library");
280         }
281
282         DCPErrorHandler error_handler;
283
284         /* All the xerces objects in this scope must be destroyed before XMLPlatformUtils::Terminate() is called */
285         {
286                 XercesDOMParser parser;
287                 parser.setValidationScheme(XercesDOMParser::Val_Always);
288                 parser.setDoNamespaces(true);
289                 parser.setDoSchema(true);
290
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");
312
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.
316                  */
317                 string locations;
318                 BOOST_FOREACH (string i, schema) {
319                         locations += String::compose("%1 %1 ", i, i);
320                 }
321
322                 parser.setExternalSchemaLocation(locations.c_str());
323                 parser.setValidationSchemaFullChecking(true);
324                 parser.setErrorHandler(&error_handler);
325
326                 LocalFileResolver resolver (xsd_dtd_directory);
327                 parser.setEntityResolver(&resolver);
328
329                 try {
330                         parser.resetDocumentPool();
331                         parse(parser, xml);
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()));
336                 } catch (...) {
337                         throw MiscError("Unknown exception from xerces");
338                 }
339         }
340
341         XMLPlatformUtils::Terminate ();
342
343         BOOST_FOREACH (XMLValidationError i, error_handler.errors()) {
344                 notes.push_back (
345                         VerificationNote(
346                                 VerificationNote::VERIFY_ERROR,
347                                 VerificationNote::XML_VALIDATION_ERROR,
348                                 i.message(),
349                                 boost::trim_copy(i.public_id() + " " + i.system_id()),
350                                 i.line()
351                                 )
352                         );
353         }
354 }
355
356
357 enum VerifyAssetResult {
358         VERIFY_ASSET_RESULT_GOOD,
359         VERIFY_ASSET_RESULT_CPL_PKL_DIFFER,
360         VERIFY_ASSET_RESULT_BAD
361 };
362
363
364 static VerifyAssetResult
365 verify_asset (shared_ptr<const DCP> dcp, shared_ptr<const ReelMXF> reel_mxf, function<void (float)> progress)
366 {
367         string const actual_hash = reel_mxf->asset_ref()->hash(progress);
368
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());
372
373         shared_ptr<Asset> asset = reel_mxf->asset_ref().asset();
374
375         optional<string> pkl_hash;
376         BOOST_FOREACH (shared_ptr<PKL> i, pkls) {
377                 pkl_hash = i->hash (reel_mxf->asset_ref()->id());
378                 if (pkl_hash) {
379                         break;
380                 }
381         }
382
383         DCP_ASSERT (pkl_hash);
384
385         optional<string> cpl_hash = reel_mxf->hash();
386         if (cpl_hash && *cpl_hash != *pkl_hash) {
387                 return VERIFY_ASSET_RESULT_CPL_PKL_DIFFER;
388         }
389
390         if (actual_hash != *pkl_hash) {
391                 return VERIFY_ASSET_RESULT_BAD;
392         }
393
394         return VERIFY_ASSET_RESULT_GOOD;
395 }
396
397
398 void
399 verify_language_tag (string tag, list<VerificationNote>& notes)
400 {
401         try {
402                 dcp::LanguageTag test (tag);
403         } catch (dcp::LanguageTagError &) {
404                 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::BAD_LANGUAGE, tag));
405         }
406 }
407
408
409 enum VerifyPictureAssetResult
410 {
411         VERIFY_PICTURE_ASSET_RESULT_GOOD,
412         VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE,
413         VERIFY_PICTURE_ASSET_RESULT_BAD,
414 };
415
416
417 int
418 biggest_frame_size (shared_ptr<const MonoPictureFrame> frame)
419 {
420         return frame->size ();
421 }
422
423 int
424 biggest_frame_size (shared_ptr<const StereoPictureFrame> frame)
425 {
426         return max(frame->left()->size(), frame->right()->size());
427 }
428
429
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)
433 {
434         shared_ptr<A> asset = dynamic_pointer_cast<A>(reel_mxf->asset_ref().asset());
435         if (!asset) {
436                 return optional<VerifyPictureAssetResult>();
437         }
438
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);
446         }
447
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;
454         }
455
456         return VERIFY_PICTURE_ASSET_RESULT_GOOD;
457 }
458
459
460 static VerifyPictureAssetResult
461 verify_picture_asset (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
462 {
463         optional<VerifyPictureAssetResult> r = verify_picture_asset_type<MonoPictureAsset, MonoPictureAssetReader, MonoPictureFrame>(reel_mxf, progress);
464         if (!r) {
465                 r = verify_picture_asset_type<StereoPictureAsset, StereoPictureAssetReader, StereoPictureFrame>(reel_mxf, progress);
466         }
467
468         DCP_ASSERT (r);
469         return *r;
470 }
471
472
473 static void
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
480         )
481 {
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);
485         switch (r) {
486                 case VERIFY_ASSET_RESULT_BAD:
487                         notes.push_back (
488                                 VerificationNote(
489                                         VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file
490                                         )
491                                 );
492                         break;
493                 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
494                         notes.push_back (
495                                 VerificationNote(
496                                         VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file
497                                         )
498                                 );
499                         break;
500                 default:
501                         break;
502         }
503         stage ("Checking picture frame sizes", reel->main_picture()->asset()->file());
504         VerifyPictureAssetResult const pr = verify_picture_asset (reel->main_picture(), progress);
505         switch (pr) {
506                 case VERIFY_PICTURE_ASSET_RESULT_BAD:
507                         notes.push_back (
508                                 VerificationNote(
509                                         VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE, file
510                                         )
511                                 );
512                         break;
513                 case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE:
514                         notes.push_back (
515                                 VerificationNote(
516                                         VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE, file
517                                         )
518                                 );
519                         break;
520                 default:
521                         break;
522         }
523 }
524
525
526 static void
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
533         )
534 {
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);
538         switch (r) {
539                 case VERIFY_ASSET_RESULT_BAD:
540                         notes.push_back (
541                                 VerificationNote(
542                                         VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *asset->file()
543                                         )
544                                 );
545                         break;
546                 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
547                         notes.push_back (
548                                 VerificationNote(
549                                         VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE, *asset->file()
550                                         )
551                                 );
552                         break;
553                 default:
554                         break;
555         }
556
557         stage ("Checking sound asset metadata", asset->file());
558
559         verify_language_tag (asset->language(), notes);
560 }
561
562
563 static void
564 verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, list<VerificationNote>& notes)
565 {
566         /* XXX: is Language compulsory? */
567         if (reel_asset->language()) {
568                 verify_language_tag (*reel_asset->language(), notes);
569         }
570 }
571
572
573 static void
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
579         )
580 {
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.
585          */
586         validate_xml (asset->raw_xml(), xsd_dtd_directory, notes);
587
588         shared_ptr<SMPTESubtitleAsset> smpte = dynamic_pointer_cast<SMPTESubtitleAsset>(asset);
589         if (smpte) {
590                 if (smpte->language()) {
591                         verify_language_tag (*smpte->language(), notes);
592                 }
593         }
594 }
595
596
597 list<VerificationNote>
598 dcp::verify (
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
603         )
604 {
605         xsd_dtd_directory = boost::filesystem::canonical (xsd_dtd_directory);
606
607         list<VerificationNote> notes;
608
609         list<shared_ptr<DCP> > dcps;
610         BOOST_FOREACH (boost::filesystem::path i, directories) {
611                 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
612         }
613
614         BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
615                 stage ("Checking DCP", dcp->directory());
616                 try {
617                         dcp->read (&notes);
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())));
626                 }
627
628                 if (dcp->standard() != dcp::SMPTE) {
629                         notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::NOT_SMPTE));
630                 }
631
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);
635
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));
641                                 }
642                         }
643
644                         BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
645                                 stage ("Checking reel", optional<boost::filesystem::path>());
646
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()));
650                                         }
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()));
653                                         }
654                                 }
655
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));
668                                         }
669                                         /* Check asset */
670                                         if (reel->main_picture()->asset_ref().resolved()) {
671                                                 verify_main_picture_asset (dcp, reel, stage, progress, notes);
672                                         }
673                                 }
674
675                                 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
676                                         verify_main_sound_asset (dcp, reel, stage, progress, notes);
677                                 }
678
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);
683                                         }
684                                 }
685                         }
686                 }
687
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);
691                 }
692
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);
696                 } else {
697                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::MISSING_ASSETMAP));
698                 }
699         }
700
701         return notes;
702 }
703
704 string
705 dcp::note_to_string (dcp::VerificationNote note)
706 {
707         switch (note.code()) {
708         case dcp::VerificationNote::GENERAL_READ:
709                 return *note.note();
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());
746         }
747
748         return "";
749 }