Tiny cleanup of using prepare_directory.
[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 <xercesc/util/PlatformUtils.hpp>
50 #include <xercesc/parsers/XercesDOMParser.hpp>
51 #include <xercesc/parsers/AbstractDOMParser.hpp>
52 #include <xercesc/sax/HandlerBase.hpp>
53 #include <xercesc/dom/DOMImplementation.hpp>
54 #include <xercesc/dom/DOMImplementationLS.hpp>
55 #include <xercesc/dom/DOMImplementationRegistry.hpp>
56 #include <xercesc/dom/DOMLSParser.hpp>
57 #include <xercesc/dom/DOMException.hpp>
58 #include <xercesc/dom/DOMDocument.hpp>
59 #include <xercesc/dom/DOMNodeList.hpp>
60 #include <xercesc/dom/DOMError.hpp>
61 #include <xercesc/dom/DOMLocator.hpp>
62 #include <xercesc/dom/DOMNamedNodeMap.hpp>
63 #include <xercesc/dom/DOMAttr.hpp>
64 #include <xercesc/dom/DOMErrorHandler.hpp>
65 #include <xercesc/framework/LocalFileInputSource.hpp>
66 #include <xercesc/framework/MemBufInputSource.hpp>
67 #include <boost/noncopyable.hpp>
68 #include <boost/foreach.hpp>
69 #include <boost/algorithm/string.hpp>
70 #include <map>
71 #include <list>
72 #include <vector>
73 #include <iostream>
74
75 using std::list;
76 using std::vector;
77 using std::string;
78 using std::cout;
79 using std::map;
80 using std::max;
81 using std::shared_ptr;
82 using boost::optional;
83 using boost::function;
84 using std::dynamic_pointer_cast;
85
86 using namespace dcp;
87 using namespace xercesc;
88
89 static
90 string
91 xml_ch_to_string (XMLCh const * a)
92 {
93         char* x = XMLString::transcode(a);
94         string const o(x);
95         XMLString::release(&x);
96         return o;
97 }
98
99 class XMLValidationError
100 {
101 public:
102         XMLValidationError (SAXParseException const & e)
103                 : _message (xml_ch_to_string(e.getMessage()))
104                 , _line (e.getLineNumber())
105                 , _column (e.getColumnNumber())
106                 , _public_id (e.getPublicId() ? xml_ch_to_string(e.getPublicId()) : "")
107                 , _system_id (e.getSystemId() ? xml_ch_to_string(e.getSystemId()) : "")
108         {
109
110         }
111
112         string message () const {
113                 return _message;
114         }
115
116         uint64_t line () const {
117                 return _line;
118         }
119
120         uint64_t column () const {
121                 return _column;
122         }
123
124         string public_id () const {
125                 return _public_id;
126         }
127
128         string system_id () const {
129                 return _system_id;
130         }
131
132 private:
133         string _message;
134         uint64_t _line;
135         uint64_t _column;
136         string _public_id;
137         string _system_id;
138 };
139
140
141 class DCPErrorHandler : public ErrorHandler
142 {
143 public:
144         void warning(const SAXParseException& e)
145         {
146                 maybe_add (XMLValidationError(e));
147         }
148
149         void error(const SAXParseException& e)
150         {
151                 maybe_add (XMLValidationError(e));
152         }
153
154         void fatalError(const SAXParseException& e)
155         {
156                 maybe_add (XMLValidationError(e));
157         }
158
159         void resetErrors() {
160                 _errors.clear ();
161         }
162
163         list<XMLValidationError> errors () const {
164                 return _errors;
165         }
166
167 private:
168         void maybe_add (XMLValidationError e)
169         {
170                 /* XXX: nasty hack */
171                 if (
172                         e.message().find("schema document") != string::npos &&
173                         e.message().find("has different target namespace from the one specified in instance document") != string::npos
174                         ) {
175                         return;
176                 }
177
178                 _errors.push_back (e);
179         }
180
181         list<XMLValidationError> _errors;
182 };
183
184 class StringToXMLCh : public boost::noncopyable
185 {
186 public:
187         StringToXMLCh (string a)
188         {
189                 _buffer = XMLString::transcode(a.c_str());
190         }
191
192         ~StringToXMLCh ()
193         {
194                 XMLString::release (&_buffer);
195         }
196
197         XMLCh const * get () const {
198                 return _buffer;
199         }
200
201 private:
202         XMLCh* _buffer;
203 };
204
205 class LocalFileResolver : public EntityResolver
206 {
207 public:
208         LocalFileResolver (boost::filesystem::path xsd_dtd_directory)
209                 : _xsd_dtd_directory (xsd_dtd_directory)
210         {
211                 /* XXX: I'm not clear on what things need to be in this list; some XSDs are apparently, magically
212                  * found without being here.
213                  */
214                 add("http://www.w3.org/2001/XMLSchema.dtd", "XMLSchema.dtd");
215                 add("http://www.w3.org/2001/03/xml.xsd", "xml.xsd");
216                 add("http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd", "xmldsig-core-schema.xsd");
217                 add("http://www.digicine.com/schemas/437-Y/2007/Main-Stereo-Picture-CPL.xsd", "Main-Stereo-Picture-CPL.xsd");
218                 add("http://www.digicine.com/PROTO-ASDCP-CPL-20040511.xsd", "PROTO-ASDCP-CPL-20040511.xsd");
219                 add("http://www.digicine.com/PROTO-ASDCP-PKL-20040311.xsd", "PROTO-ASDCP-PKL-20040311.xsd");
220                 add("http://www.digicine.com/PROTO-ASDCP-AM-20040311.xsd", "PROTO-ASDCP-AM-20040311.xsd");
221                 add("http://www.digicine.com/PROTO-ASDCP-CC-CPL-20070926#", "PROTO-ASDCP-CC-CPL-20070926.xsd");
222                 add("interop-subs", "DCSubtitle.v1.mattsson.xsd");
223                 add("http://www.smpte-ra.org/schemas/428-7/2010/DCST.xsd", "SMPTE-428-7-2010-DCST.xsd");
224                 add("http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata", "SMPTE-429-16.xsd");
225                 add("http://www.dolby.com/schemas/2012/AD", "Dolby-2012-AD.xsd");
226                 add("http://www.smpte-ra.org/schemas/429-10/2008/Main-Stereo-Picture-CPL", "SMPTE-429-10-2008.xsd");
227         }
228
229         InputSource* resolveEntity(XMLCh const *, XMLCh const * system_id)
230         {
231                 if (!system_id) {
232                         return 0;
233                 }
234                 string system_id_str = xml_ch_to_string (system_id);
235                 boost::filesystem::path p = _xsd_dtd_directory;
236                 if (_files.find(system_id_str) == _files.end()) {
237                         p /= system_id_str;
238                 } else {
239                         p /= _files[system_id_str];
240                 }
241                 StringToXMLCh ch (p.string());
242                 return new LocalFileInputSource(ch.get());
243         }
244
245 private:
246         void add (string uri, string file)
247         {
248                 _files[uri] = file;
249         }
250
251         std::map<string, string> _files;
252         boost::filesystem::path _xsd_dtd_directory;
253 };
254
255
256 static void
257 parse (XercesDOMParser& parser, boost::filesystem::path xml)
258 {
259         parser.parse(xml.string().c_str());
260 }
261
262
263 static void
264 parse (XercesDOMParser& parser, std::string xml)
265 {
266         xercesc::MemBufInputSource buf(reinterpret_cast<unsigned char const*>(xml.c_str()), xml.size(), "");
267         parser.parse(buf);
268 }
269
270
271 template <class T>
272 void
273 validate_xml (T xml, boost::filesystem::path xsd_dtd_directory, list<VerificationNote>& notes)
274 {
275         try {
276                 XMLPlatformUtils::Initialize ();
277         } catch (XMLException& e) {
278                 throw MiscError ("Failed to initialise xerces library");
279         }
280
281         DCPErrorHandler error_handler;
282
283         /* All the xerces objects in this scope must be destroyed before XMLPlatformUtils::Terminate() is called */
284         {
285                 XercesDOMParser parser;
286                 parser.setValidationScheme(XercesDOMParser::Val_Always);
287                 parser.setDoNamespaces(true);
288                 parser.setDoSchema(true);
289
290                 vector<string> schema;
291                 schema.push_back("xml.xsd");
292                 schema.push_back("xmldsig-core-schema.xsd");
293                 schema.push_back("SMPTE-429-7-2006-CPL.xsd");
294                 schema.push_back("SMPTE-429-8-2006-PKL.xsd");
295                 schema.push_back("SMPTE-429-9-2007-AM.xsd");
296                 schema.push_back("Main-Stereo-Picture-CPL.xsd");
297                 schema.push_back("PROTO-ASDCP-CPL-20040511.xsd");
298                 schema.push_back("PROTO-ASDCP-PKL-20040311.xsd");
299                 schema.push_back("PROTO-ASDCP-AM-20040311.xsd");
300                 schema.push_back("DCSubtitle.v1.mattsson.xsd");
301                 schema.push_back("DCDMSubtitle-2010.xsd");
302                 schema.push_back("PROTO-ASDCP-CC-CPL-20070926.xsd");
303                 schema.push_back("SMPTE-429-16.xsd");
304                 schema.push_back("Dolby-2012-AD.xsd");
305                 schema.push_back("SMPTE-429-10-2008.xsd");
306                 schema.push_back("xlink.xsd");
307                 schema.push_back("SMPTE-335-2012.xsd");
308                 schema.push_back("SMPTE-395-2014-13-1-aaf.xsd");
309                 schema.push_back("isdcf-mca.xsd");
310                 schema.push_back("SMPTE-429-12-2008.xsd");
311
312                 /* XXX: I'm not especially clear what this is for, but it seems to be necessary.
313                  * Schemas that are not mentioned in this list are not read, and the things
314                  * they describe are not checked.
315                  */
316                 string locations;
317                 BOOST_FOREACH (string i, schema) {
318                         locations += String::compose("%1 %1 ", i, i);
319                 }
320
321                 parser.setExternalSchemaLocation(locations.c_str());
322                 parser.setValidationSchemaFullChecking(true);
323                 parser.setErrorHandler(&error_handler);
324
325                 LocalFileResolver resolver (xsd_dtd_directory);
326                 parser.setEntityResolver(&resolver);
327
328                 try {
329                         parser.resetDocumentPool();
330                         parse(parser, xml);
331                 } catch (XMLException& e) {
332                         throw MiscError(xml_ch_to_string(e.getMessage()));
333                 } catch (DOMException& e) {
334                         throw MiscError(xml_ch_to_string(e.getMessage()));
335                 } catch (...) {
336                         throw MiscError("Unknown exception from xerces");
337                 }
338         }
339
340         XMLPlatformUtils::Terminate ();
341
342         BOOST_FOREACH (XMLValidationError i, error_handler.errors()) {
343                 notes.push_back (
344                         VerificationNote(
345                                 VerificationNote::VERIFY_ERROR,
346                                 VerificationNote::XML_VALIDATION_ERROR,
347                                 i.message(),
348                                 boost::trim_copy(i.public_id() + " " + i.system_id()),
349                                 i.line()
350                                 )
351                         );
352         }
353 }
354
355
356 enum VerifyAssetResult {
357         VERIFY_ASSET_RESULT_GOOD,
358         VERIFY_ASSET_RESULT_CPL_PKL_DIFFER,
359         VERIFY_ASSET_RESULT_BAD
360 };
361
362
363 static VerifyAssetResult
364 verify_asset (shared_ptr<const DCP> dcp, shared_ptr<const ReelMXF> reel_mxf, function<void (float)> progress)
365 {
366         string const actual_hash = reel_mxf->asset_ref()->hash(progress);
367
368         list<shared_ptr<PKL> > pkls = dcp->pkls();
369         /* We've read this DCP in so it must have at least one PKL */
370         DCP_ASSERT (!pkls.empty());
371
372         shared_ptr<Asset> asset = reel_mxf->asset_ref().asset();
373
374         optional<string> pkl_hash;
375         BOOST_FOREACH (shared_ptr<PKL> i, pkls) {
376                 pkl_hash = i->hash (reel_mxf->asset_ref()->id());
377                 if (pkl_hash) {
378                         break;
379                 }
380         }
381
382         DCP_ASSERT (pkl_hash);
383
384         optional<string> cpl_hash = reel_mxf->hash();
385         if (cpl_hash && *cpl_hash != *pkl_hash) {
386                 return VERIFY_ASSET_RESULT_CPL_PKL_DIFFER;
387         }
388
389         if (actual_hash != *pkl_hash) {
390                 return VERIFY_ASSET_RESULT_BAD;
391         }
392
393         return VERIFY_ASSET_RESULT_GOOD;
394 }
395
396
397 void
398 verify_language_tag (string tag, list<VerificationNote>& notes)
399 {
400         try {
401                 dcp::LanguageTag test (tag);
402         } catch (dcp::LanguageTagError &) {
403                 notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::BAD_LANGUAGE, tag));
404         }
405 }
406
407
408 enum VerifyPictureAssetResult
409 {
410         VERIFY_PICTURE_ASSET_RESULT_GOOD,
411         VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE,
412         VERIFY_PICTURE_ASSET_RESULT_BAD,
413 };
414
415
416 int
417 biggest_frame_size (shared_ptr<const MonoPictureFrame> frame)
418 {
419         return frame->size ();
420 }
421
422 int
423 biggest_frame_size (shared_ptr<const StereoPictureFrame> frame)
424 {
425         return max(frame->left()->size(), frame->right()->size());
426 }
427
428
429 template <class A, class R, class F>
430 optional<VerifyPictureAssetResult>
431 verify_picture_asset_type (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
432 {
433         shared_ptr<A> asset = dynamic_pointer_cast<A>(reel_mxf->asset_ref().asset());
434         if (!asset) {
435                 return optional<VerifyPictureAssetResult>();
436         }
437
438         int biggest_frame = 0;
439         shared_ptr<R> reader = asset->start_read ();
440         int64_t const duration = asset->intrinsic_duration ();
441         for (int64_t i = 0; i < duration; ++i) {
442                 shared_ptr<const F> frame = reader->get_frame (i);
443                 biggest_frame = max(biggest_frame, biggest_frame_size(frame));
444                 progress (float(i) / duration);
445         }
446
447         static const int max_frame =   rint(250 * 1000000 / (8 * asset->edit_rate().as_float()));
448         static const int risky_frame = rint(230 * 1000000 / (8 * asset->edit_rate().as_float()));
449         if (biggest_frame > max_frame) {
450                 return VERIFY_PICTURE_ASSET_RESULT_BAD;
451         } else if (biggest_frame > risky_frame) {
452                 return VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE;
453         }
454
455         return VERIFY_PICTURE_ASSET_RESULT_GOOD;
456 }
457
458
459 static VerifyPictureAssetResult
460 verify_picture_asset (shared_ptr<ReelMXF> reel_mxf, function<void (float)> progress)
461 {
462         optional<VerifyPictureAssetResult> r = verify_picture_asset_type<MonoPictureAsset, MonoPictureAssetReader, MonoPictureFrame>(reel_mxf, progress);
463         if (!r) {
464                 r = verify_picture_asset_type<StereoPictureAsset, StereoPictureAssetReader, StereoPictureFrame>(reel_mxf, progress);
465         }
466
467         DCP_ASSERT (r);
468         return *r;
469 }
470
471
472 static void
473 verify_main_picture_asset (
474         shared_ptr<const DCP> dcp,
475         shared_ptr<const Reel> reel,
476         function<void (string, optional<boost::filesystem::path>)> stage,
477         function<void (float)> progress,
478         list<VerificationNote>& notes
479         )
480 {
481         boost::filesystem::path const file = *reel->main_picture()->asset()->file();
482         stage ("Checking picture asset hash", file);
483         VerifyAssetResult const r = verify_asset (dcp, reel->main_picture(), progress);
484         switch (r) {
485                 case VERIFY_ASSET_RESULT_BAD:
486                         notes.push_back (
487                                 VerificationNote(
488                                         VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_HASH_INCORRECT, file
489                                         )
490                                 );
491                         break;
492                 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
493                         notes.push_back (
494                                 VerificationNote(
495                                         VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE, file
496                                         )
497                                 );
498                         break;
499                 default:
500                         break;
501         }
502         stage ("Checking picture frame sizes", reel->main_picture()->asset()->file());
503         VerifyPictureAssetResult const pr = verify_picture_asset (reel->main_picture(), progress);
504         switch (pr) {
505                 case VERIFY_PICTURE_ASSET_RESULT_BAD:
506                         notes.push_back (
507                                 VerificationNote(
508                                         VerificationNote::VERIFY_ERROR, VerificationNote::PICTURE_FRAME_TOO_LARGE, file
509                                         )
510                                 );
511                         break;
512                 case VERIFY_PICTURE_ASSET_RESULT_FRAME_NEARLY_TOO_LARGE:
513                         notes.push_back (
514                                 VerificationNote(
515                                         VerificationNote::VERIFY_WARNING, VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE, file
516                                         )
517                                 );
518                         break;
519                 default:
520                         break;
521         }
522 }
523
524
525 static void
526 verify_main_sound_asset (
527         shared_ptr<const DCP> dcp,
528         shared_ptr<const Reel> reel,
529         function<void (string, optional<boost::filesystem::path>)> stage,
530         function<void (float)> progress,
531         list<VerificationNote>& notes
532         )
533 {
534         stage ("Checking sound asset hash", reel->main_sound()->asset()->file());
535         VerifyAssetResult const r = verify_asset (dcp, reel->main_sound(), progress);
536         switch (r) {
537                 case VERIFY_ASSET_RESULT_BAD:
538                         notes.push_back (
539                                 VerificationNote(
540                                         VerificationNote::VERIFY_ERROR, VerificationNote::SOUND_HASH_INCORRECT, *reel->main_sound()->asset()->file()
541                                         )
542                                 );
543                         break;
544                 case VERIFY_ASSET_RESULT_CPL_PKL_DIFFER:
545                         notes.push_back (
546                                 VerificationNote(
547                                         VerificationNote::VERIFY_ERROR, VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE, *reel->main_sound()->asset()->file()
548                                         )
549                                 );
550                         break;
551                 default:
552                         break;
553         }
554 }
555
556
557 static void
558 verify_main_subtitle_reel (shared_ptr<const ReelSubtitleAsset> reel_asset, list<VerificationNote>& notes)
559 {
560         /* XXX: is Language compulsory? */
561         if (reel_asset->language()) {
562                 verify_language_tag (*reel_asset->language(), notes);
563         }
564 }
565
566
567 static void
568 verify_main_subtitle_asset (
569         shared_ptr<const Reel> reel,
570         function<void (string, optional<boost::filesystem::path>)> stage,
571         boost::filesystem::path xsd_dtd_directory,
572         list<VerificationNote>& notes
573         )
574 {
575         shared_ptr<ReelSubtitleAsset> reel_asset = reel->main_subtitle ();
576         stage ("Checking subtitle XML", reel->main_subtitle()->asset()->file());
577         /* Note: we must not use SubtitleAsset::xml_as_string() here as that will mean the data on disk
578          * gets passed through libdcp which may clean up and therefore hide errors.
579          */
580         validate_xml (reel->main_subtitle()->asset()->raw_xml(), xsd_dtd_directory, notes);
581 }
582
583
584 list<VerificationNote>
585 dcp::verify (
586         vector<boost::filesystem::path> directories,
587         function<void (string, optional<boost::filesystem::path>)> stage,
588         function<void (float)> progress,
589         boost::filesystem::path xsd_dtd_directory
590         )
591 {
592         xsd_dtd_directory = boost::filesystem::canonical (xsd_dtd_directory);
593
594         list<VerificationNote> notes;
595
596         list<shared_ptr<DCP> > dcps;
597         BOOST_FOREACH (boost::filesystem::path i, directories) {
598                 dcps.push_back (shared_ptr<DCP> (new DCP (i)));
599         }
600
601         BOOST_FOREACH (shared_ptr<DCP> dcp, dcps) {
602                 stage ("Checking DCP", dcp->directory());
603                 try {
604                         dcp->read (&notes);
605                 } catch (ReadError& e) {
606                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
607                 } catch (XMLError& e) {
608                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
609                 } catch (MXFFileError& e) {
610                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
611                 } catch (cxml::Error& e) {
612                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::GENERAL_READ, string(e.what())));
613                 }
614
615                 if (dcp->standard() != dcp::SMPTE) {
616                         notes.push_back (VerificationNote(VerificationNote::VERIFY_BV21_ERROR, VerificationNote::NOT_SMPTE));
617                 }
618
619                 BOOST_FOREACH (shared_ptr<CPL> cpl, dcp->cpls()) {
620                         stage ("Checking CPL", cpl->file());
621                         validate_xml (cpl->file().get(), xsd_dtd_directory, notes);
622
623                         /* Check that the CPL's hash corresponds to the PKL */
624                         BOOST_FOREACH (shared_ptr<PKL> i, dcp->pkls()) {
625                                 optional<string> h = i->hash(cpl->id());
626                                 if (h && make_digest(ArrayData(*cpl->file())) != *h) {
627                                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::CPL_HASH_INCORRECT));
628                                 }
629                         }
630
631                         BOOST_FOREACH (shared_ptr<Reel> reel, cpl->reels()) {
632                                 stage ("Checking reel", optional<boost::filesystem::path>());
633
634                                 BOOST_FOREACH (shared_ptr<ReelAsset> i, reel->assets()) {
635                                         if (i->duration() && (i->duration().get() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
636                                                 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::DURATION_TOO_SMALL, i->id()));
637                                         }
638                                         if ((i->intrinsic_duration() * i->edit_rate().denominator / i->edit_rate().numerator) < 1) {
639                                                 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INTRINSIC_DURATION_TOO_SMALL, i->id()));
640                                         }
641                                 }
642
643                                 if (reel->main_picture()) {
644                                         /* Check reel stuff */
645                                         Fraction const frame_rate = reel->main_picture()->frame_rate();
646                                         if (frame_rate.denominator != 1 ||
647                                             (frame_rate.numerator != 24 &&
648                                              frame_rate.numerator != 25 &&
649                                              frame_rate.numerator != 30 &&
650                                              frame_rate.numerator != 48 &&
651                                              frame_rate.numerator != 50 &&
652                                              frame_rate.numerator != 60 &&
653                                              frame_rate.numerator != 96)) {
654                                                 notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::INVALID_PICTURE_FRAME_RATE));
655                                         }
656                                         /* Check asset */
657                                         if (reel->main_picture()->asset_ref().resolved()) {
658                                                 verify_main_picture_asset (dcp, reel, stage, progress, notes);
659                                         }
660                                 }
661
662                                 if (reel->main_sound() && reel->main_sound()->asset_ref().resolved()) {
663                                         verify_main_sound_asset (dcp, reel, stage, progress, notes);
664                                 }
665
666                                 if (reel->main_subtitle()) {
667                                         verify_main_subtitle_reel (reel->main_subtitle(), notes);
668                                         if (reel->main_subtitle()->asset_ref().resolved()) {
669                                                 verify_main_subtitle_asset (reel, stage, xsd_dtd_directory, notes);
670                                         }
671                                 }
672                         }
673                 }
674
675                 BOOST_FOREACH (shared_ptr<PKL> pkl, dcp->pkls()) {
676                         stage ("Checking PKL", pkl->file());
677                         validate_xml (pkl->file().get(), xsd_dtd_directory, notes);
678                 }
679
680                 if (dcp->asset_map_path()) {
681                         stage ("Checking ASSETMAP", dcp->asset_map_path().get());
682                         validate_xml (dcp->asset_map_path().get(), xsd_dtd_directory, notes);
683                 } else {
684                         notes.push_back (VerificationNote(VerificationNote::VERIFY_ERROR, VerificationNote::MISSING_ASSETMAP));
685                 }
686         }
687
688         return notes;
689 }
690
691 string
692 dcp::note_to_string (dcp::VerificationNote note)
693 {
694         switch (note.code()) {
695         case dcp::VerificationNote::GENERAL_READ:
696                 return *note.note();
697         case dcp::VerificationNote::CPL_HASH_INCORRECT:
698                 return "The hash of the CPL in the PKL does not agree with the CPL file.";
699         case dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE:
700                 return "The picture in a reel has an invalid frame rate.";
701         case dcp::VerificationNote::PICTURE_HASH_INCORRECT:
702                 return dcp::String::compose("The hash of the picture asset %1 does not agree with the PKL file.", note.file()->filename());
703         case dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE:
704                 return dcp::String::compose("The PKL and CPL hashes disagree for the picture asset %1.", note.file()->filename());
705         case dcp::VerificationNote::SOUND_HASH_INCORRECT:
706                 return dcp::String::compose("The hash of the sound asset %1 does not agree with the PKL file.", note.file()->filename());
707         case dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE:
708                 return dcp::String::compose("The PKL and CPL hashes disagree for the sound asset %1.", note.file()->filename());
709         case dcp::VerificationNote::EMPTY_ASSET_PATH:
710                 return "The asset map contains an empty asset path.";
711         case dcp::VerificationNote::MISSING_ASSET:
712                 return String::compose("The file for an asset in the asset map cannot be found; missing file is %1.", note.file()->filename());
713         case dcp::VerificationNote::MISMATCHED_STANDARD:
714                 return "The DCP contains both SMPTE and Interop parts.";
715         case dcp::VerificationNote::XML_VALIDATION_ERROR:
716                 return String::compose("An XML file is badly formed: %1 (%2:%3)", note.note().get(), note.file()->filename(), note.line().get());
717         case dcp::VerificationNote::MISSING_ASSETMAP:
718                 return "No ASSETMAP or ASSETMAP.xml was found.";
719         case dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL:
720                 return String::compose("The intrinsic duration of an asset is less than 1 second long: %1", note.note().get());
721         case dcp::VerificationNote::DURATION_TOO_SMALL:
722                 return String::compose("The duration of an asset is less than 1 second long: %1", note.note().get());
723         case dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE:
724                 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());
725         case dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE:
726                 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());
727         case dcp::VerificationNote::EXTERNAL_ASSET:
728                 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());
729         case dcp::VerificationNote::NOT_SMPTE:
730                 return "This DCP does not use the SMPTE standard, which is required for Bv2.1 compliance.";
731         case dcp::VerificationNote::BAD_LANGUAGE:
732                 return String::compose("The DCP specifies a language '%1' which does not conform to the RFC 5646 standard.", note.note().get());
733         }
734
735         return "";
736 }