We can always dump notes where there are none expected.
[libdcp.git] / test / verify_test.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 "util.h"
36 #include "j2k.h"
37 #include "reel.h"
38 #include "reel_mono_picture_asset.h"
39 #include "cpl.h"
40 #include "dcp.h"
41 #include "openjpeg_image.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_asset_writer.h"
44 #include "interop_subtitle_asset.h"
45 #include "smpte_subtitle_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "compose.hpp"
48 #include "test.h"
49 #include <boost/test/unit_test.hpp>
50 #include <boost/foreach.hpp>
51 #include <boost/algorithm/string.hpp>
52 #include <cstdio>
53 #include <iostream>
54
55 using std::list;
56 using std::pair;
57 using std::string;
58 using std::vector;
59 using std::make_pair;
60 using boost::optional;
61 using std::shared_ptr;
62
63
64 static list<pair<string, optional<boost::filesystem::path> > > stages;
65
66 static void
67 stage (string s, optional<boost::filesystem::path> p)
68 {
69         stages.push_back (make_pair (s, p));
70 }
71
72 static void
73 progress (float)
74 {
75
76 }
77
78 static void
79 prepare_directory (boost::filesystem::path path)
80 {
81         using namespace boost::filesystem;
82         remove_all (path);
83         create_directories (path);
84 }
85
86
87 static vector<boost::filesystem::path>
88 setup (int reference_number, int verify_test_number)
89 {
90         prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
91         for (boost::filesystem::directory_iterator i(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number)); i != boost::filesystem::directory_iterator(); ++i) {
92                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i->path().filename());
93         }
94
95         vector<boost::filesystem::path> directories;
96         directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
97         return directories;
98
99 }
100
101
102 /** Class that can alter a file by searching and replacing strings within it.
103  *  On destruction modifies the file whose name was given to the constructor.
104  */
105 class Editor
106 {
107 public:
108         Editor (boost::filesystem::path path)
109                 : _path(path)
110         {
111                 _content = dcp::file_to_string (_path);
112         }
113
114         ~Editor ()
115         {
116                 FILE* f = fopen(_path.string().c_str(), "w");
117                 BOOST_REQUIRE (f);
118                 fwrite (_content.c_str(), _content.length(), 1, f);
119                 fclose (f);
120         }
121
122         void replace (string a, string b)
123         {
124                 boost::algorithm::replace_all (_content, a, b);
125         }
126
127 private:
128         boost::filesystem::path _path;
129         std::string _content;
130 };
131
132
133 static
134 void
135 dump_notes (list<dcp::VerificationNote> const & notes)
136 {
137         BOOST_FOREACH (dcp::VerificationNote i, notes) {
138                 std::cout << dcp::note_to_string(i) << "\n";
139         }
140 }
141
142 /* Check DCP as-is (should be OK) */
143 BOOST_AUTO_TEST_CASE (verify_test1)
144 {
145         stages.clear ();
146         vector<boost::filesystem::path> directories = setup (1, 1);
147         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
148
149         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
150         boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml";
151         boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
152
153         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
154         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
155         BOOST_REQUIRE (st->second);
156         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
157         ++st;
158         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
159         BOOST_REQUIRE (st->second);
160         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
161         ++st;
162         BOOST_CHECK_EQUAL (st->first, "Checking reel");
163         BOOST_REQUIRE (!st->second);
164         ++st;
165         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
166         BOOST_REQUIRE (st->second);
167         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
168         ++st;
169         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
170         BOOST_REQUIRE (st->second);
171         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
172         ++st;
173         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
174         BOOST_REQUIRE (st->second);
175         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
176         ++st;
177         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
178         BOOST_REQUIRE (st->second);
179         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
180         ++st;
181         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
182         BOOST_REQUIRE (st->second);
183         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
184         ++st;
185         BOOST_REQUIRE (st == stages.end());
186
187         BOOST_CHECK_EQUAL (notes.size(), 0);
188 }
189
190 /* Corrupt the MXFs and check that this is spotted */
191 BOOST_AUTO_TEST_CASE (verify_test2)
192 {
193         vector<boost::filesystem::path> directories = setup (1, 2);
194
195         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
196         BOOST_REQUIRE (mod);
197         fseek (mod, 4096, SEEK_SET);
198         int x = 42;
199         fwrite (&x, sizeof(x), 1, mod);
200         fclose (mod);
201
202         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
203         BOOST_REQUIRE (mod);
204         BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
205         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
206         fclose (mod);
207
208         list<dcp::VerificationNote> notes;
209         {
210                 dcp::ASDCPErrorSuspender sus;
211                 notes = dcp::verify (directories, &stage, &progress, xsd_test);
212         }
213
214         BOOST_REQUIRE_EQUAL (notes.size(), 2);
215         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
216         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
217         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
218         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
219 }
220
221 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
222 BOOST_AUTO_TEST_CASE (verify_test3)
223 {
224         vector<boost::filesystem::path> directories = setup (1, 3);
225
226         {
227                 Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml");
228                 e.replace ("<Hash>", "<Hash>x");
229         }
230
231         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
232
233         BOOST_REQUIRE_EQUAL (notes.size(), 6);
234         list<dcp::VerificationNote>::const_iterator i = notes.begin();
235         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
236         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
237         ++i;
238         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
239         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
240         ++i;
241         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
242         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
243         ++i;
244         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
245         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
246         ++i;
247         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
248         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
249         ++i;
250         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
251         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
252         ++i;
253 }
254
255 /* Corrupt the ContentKind in the CPL */
256 BOOST_AUTO_TEST_CASE (verify_test4)
257 {
258         vector<boost::filesystem::path> directories = setup (1, 4);
259
260         {
261                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
262                 e.replace ("<ContentKind>", "<ContentKind>x");
263         }
264
265         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
266
267         BOOST_REQUIRE_EQUAL (notes.size(), 1);
268         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
269         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
270 }
271
272 static
273 boost::filesystem::path
274 cpl (int n)
275 {
276         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
277 }
278
279 static
280 boost::filesystem::path
281 pkl (int n)
282 {
283         return dcp::String::compose("build/test/verify_test%1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n);
284 }
285
286 static
287 boost::filesystem::path
288 asset_map (int n)
289 {
290         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
291 }
292
293 static
294 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
295 {
296         vector<boost::filesystem::path> directories = setup (1, n);
297
298         {
299                 Editor e (file(n));
300                 e.replace (from, to);
301         }
302
303         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
304
305         BOOST_REQUIRE_EQUAL (notes.size(), 1);
306         BOOST_CHECK_EQUAL (notes.front().code(), code1);
307 }
308
309 static
310 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1, dcp::VerificationNote::Code code2)
311 {
312         vector<boost::filesystem::path> directories = setup (1, n);
313
314         {
315                 Editor e (file(n));
316                 e.replace (from, to);
317         }
318
319         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
320
321         BOOST_REQUIRE_EQUAL (notes.size(), 2);
322         BOOST_CHECK_EQUAL (notes.front().code(), code1);
323         BOOST_CHECK_EQUAL (notes.back().code(), code2);
324 }
325
326 static
327 void check_after_replace (
328         int n, boost::function<boost::filesystem::path (int)> file,
329         string from,
330         string to,
331         dcp::VerificationNote::Code code1,
332         dcp::VerificationNote::Code code2,
333         dcp::VerificationNote::Code code3
334         )
335 {
336         vector<boost::filesystem::path> directories = setup (1, n);
337
338         {
339                 Editor e (file(n));
340                 e.replace (from, to);
341         }
342
343         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
344
345         BOOST_REQUIRE_EQUAL (notes.size(), 3);
346         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
347         BOOST_CHECK_EQUAL (i->code(), code1);
348         ++i;
349         BOOST_CHECK_EQUAL (i->code(), code2);
350         ++i;
351         BOOST_CHECK_EQUAL (i->code(), code3);
352 }
353
354 /* FrameRate */
355 BOOST_AUTO_TEST_CASE (verify_test5)
356 {
357         check_after_replace (
358                         5, &cpl,
359                         "<FrameRate>24 1", "<FrameRate>99 1",
360                         dcp::VerificationNote::CPL_HASH_INCORRECT,
361                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
362                         );
363 }
364
365 /* Missing asset */
366 BOOST_AUTO_TEST_CASE (verify_test6)
367 {
368         vector<boost::filesystem::path> directories = setup (1, 6);
369
370         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
371         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
372
373         BOOST_REQUIRE_EQUAL (notes.size(), 1);
374         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
375         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
376 }
377
378 static
379 boost::filesystem::path
380 assetmap (int n)
381 {
382         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
383 }
384
385 /* Empty asset filename in ASSETMAP */
386 BOOST_AUTO_TEST_CASE (verify_test7)
387 {
388         check_after_replace (
389                         7, &assetmap,
390                         "<Path>video.mxf</Path>", "<Path></Path>",
391                         dcp::VerificationNote::EMPTY_ASSET_PATH
392                         );
393 }
394
395 /* Mismatched standard */
396 BOOST_AUTO_TEST_CASE (verify_test8)
397 {
398         check_after_replace (
399                         8, &cpl,
400                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
401                         dcp::VerificationNote::MISMATCHED_STANDARD,
402                         dcp::VerificationNote::XML_VALIDATION_ERROR,
403                         dcp::VerificationNote::CPL_HASH_INCORRECT
404                         );
405 }
406
407 /* Badly formatted <Id> in CPL */
408 BOOST_AUTO_TEST_CASE (verify_test9)
409 {
410         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
411         check_after_replace (
412                         9, &cpl,
413                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
414                         dcp::VerificationNote::XML_VALIDATION_ERROR
415                         );
416 }
417
418 /* Badly formatted <IssueDate> in CPL */
419 BOOST_AUTO_TEST_CASE (verify_test10)
420 {
421         check_after_replace (
422                         10, &cpl,
423                         "<IssueDate>", "<IssueDate>x",
424                         dcp::VerificationNote::XML_VALIDATION_ERROR,
425                         dcp::VerificationNote::CPL_HASH_INCORRECT
426                         );
427 }
428
429 /* Badly-formatted <Id> in PKL */
430 BOOST_AUTO_TEST_CASE (verify_test11)
431 {
432         check_after_replace (
433                 11, &pkl,
434                 "<Id>urn:uuid:cd4", "<Id>urn:uuid:xd4",
435                 dcp::VerificationNote::XML_VALIDATION_ERROR
436                 );
437 }
438
439 /* Badly-formatted <Id> in ASSETMAP */
440 BOOST_AUTO_TEST_CASE (verify_test12)
441 {
442         check_after_replace (
443                 12, &asset_map,
444                 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
445                 dcp::VerificationNote::XML_VALIDATION_ERROR
446                 );
447 }
448
449 /* Basic test of an Interop DCP */
450 BOOST_AUTO_TEST_CASE (verify_test13)
451 {
452         stages.clear ();
453         vector<boost::filesystem::path> directories = setup (3, 13);
454         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
455
456         boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
457         boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
458         boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
459
460         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
461         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
462         BOOST_REQUIRE (st->second);
463         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
464         ++st;
465         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
466         BOOST_REQUIRE (st->second);
467         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
468         ++st;
469         BOOST_CHECK_EQUAL (st->first, "Checking reel");
470         BOOST_REQUIRE (!st->second);
471         ++st;
472         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
473         BOOST_REQUIRE (st->second);
474         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
475         ++st;
476         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
477         BOOST_REQUIRE (st->second);
478         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
479         ++st;
480         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
481         BOOST_REQUIRE (st->second);
482         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
483         ++st;
484         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
485         BOOST_REQUIRE (st->second);
486         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
487         ++st;
488         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
489         BOOST_REQUIRE (st->second);
490         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
491         ++st;
492         BOOST_REQUIRE (st == stages.end());
493
494         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
495         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
496         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
497         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
498 }
499
500 /* DCP with a short asset */
501 BOOST_AUTO_TEST_CASE (verify_test14)
502 {
503         vector<boost::filesystem::path> directories = setup (8, 14);
504         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
505
506         BOOST_REQUIRE_EQUAL (notes.size(), 5);
507         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
508         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
509         ++i;
510         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
511         ++i;
512         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
513         ++i;
514         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
515         ++i;
516         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
517         ++i;
518 }
519
520
521 static
522 void
523 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
524 {
525         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
526         boost::filesystem::create_directories (dir);
527         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
528         for (int i = 0; i < 24; ++i) {
529                 writer->write (frame.data(), frame.size());
530         }
531         writer->finalize ();
532
533         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelMonoPictureAsset(asset, 0));
534         shared_ptr<dcp::Reel> reel(new dcp::Reel());
535         reel->add (reel_asset);
536         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
537         cpl->add (reel);
538         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
539         dcp->add (cpl);
540         dcp->write_xml (dcp::SMPTE);
541 }
542
543
544 /* DCP with an over-sized JPEG2000 frame */
545 BOOST_AUTO_TEST_CASE (verify_test15)
546 {
547         int const too_big = 1302083 * 2;
548
549         /* Compress a black image */
550         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
551         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
552         BOOST_REQUIRE (frame.size() < too_big);
553
554         /* Place it in a bigger block with some zero padding at the end */
555         dcp::ArrayData oversized_frame(too_big);
556         memcpy (oversized_frame.data(), frame.data(), frame.size());
557         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
558
559         boost::filesystem::path const dir("build/test/verify_test15");
560         boost::filesystem::remove_all (dir);
561         dcp_from_frame (oversized_frame, dir);
562
563         vector<boost::filesystem::path> dirs;
564         dirs.push_back (dir);
565         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
566         BOOST_REQUIRE_EQUAL (notes.size(), 1);
567         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE);
568 }
569
570
571 /* DCP with a nearly over-sized JPEG2000 frame */
572 BOOST_AUTO_TEST_CASE (verify_test16)
573 {
574         int const nearly_too_big = 1302083 * 0.98;
575
576         /* Compress a black image */
577         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
578         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
579         BOOST_REQUIRE (frame.size() < nearly_too_big);
580
581         /* Place it in a bigger block with some zero padding at the end */
582         dcp::ArrayData oversized_frame(nearly_too_big);
583         memcpy (oversized_frame.data(), frame.data(), frame.size());
584         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
585
586         boost::filesystem::path const dir("build/test/verify_test16");
587         boost::filesystem::remove_all (dir);
588         dcp_from_frame (oversized_frame, dir);
589
590         vector<boost::filesystem::path> dirs;
591         dirs.push_back (dir);
592         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
593         BOOST_REQUIRE_EQUAL (notes.size(), 1);
594         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE);
595 }
596
597
598 /* DCP with a within-range JPEG2000 frame */
599 BOOST_AUTO_TEST_CASE (verify_test17)
600 {
601         /* Compress a black image */
602         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
603         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
604         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
605
606         boost::filesystem::path const dir("build/test/verify_test17");
607         boost::filesystem::remove_all (dir);
608         dcp_from_frame (frame, dir);
609
610         vector<boost::filesystem::path> dirs;
611         dirs.push_back (dir);
612         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
613         BOOST_REQUIRE_EQUAL (notes.size(), 0);
614 }
615
616
617 /* DCP with valid Interop subtitles */
618 BOOST_AUTO_TEST_CASE (verify_test18)
619 {
620         boost::filesystem::path const dir("build/test/verify_test18");
621         prepare_directory (dir);
622         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
623         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
624         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
625         shared_ptr<dcp::Reel> reel(new dcp::Reel());
626         reel->add (reel_asset);
627         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
628         cpl->add (reel);
629         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
630         dcp->add (cpl);
631         dcp->write_xml (dcp::INTEROP);
632
633         vector<boost::filesystem::path> dirs;
634         dirs.push_back (dir);
635         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
636         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
637         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
638         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
639 }
640
641
642 /* DCP with broken Interop subtitles */
643 BOOST_AUTO_TEST_CASE (verify_test19)
644 {
645         boost::filesystem::path const dir("build/test/verify_test19");
646         prepare_directory (dir);
647         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
648         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
649         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
650         shared_ptr<dcp::Reel> reel(new dcp::Reel());
651         reel->add (reel_asset);
652         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
653         cpl->add (reel);
654         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
655         dcp->add (cpl);
656         dcp->write_xml (dcp::INTEROP);
657
658         {
659                 Editor e (dir / "subs.xml");
660                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
661         }
662
663         vector<boost::filesystem::path> dirs;
664         dirs.push_back (dir);
665         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
666         BOOST_REQUIRE_EQUAL (notes.size(), 3);
667         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
668         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
669         ++i;
670         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
671         ++i;
672         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
673         ++i;
674 }
675
676
677 /* DCP with valid SMPTE subtitles */
678 BOOST_AUTO_TEST_CASE (verify_test20)
679 {
680         boost::filesystem::path const dir("build/test/verify_test20");
681         prepare_directory (dir);
682         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
683         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
684         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
685         shared_ptr<dcp::Reel> reel(new dcp::Reel());
686         reel->add (reel_asset);
687         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
688         cpl->add (reel);
689         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
690         dcp->add (cpl);
691         dcp->write_xml (dcp::SMPTE);
692
693         vector<boost::filesystem::path> dirs;
694         dirs.push_back (dir);
695         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
696         dump_notes (notes);
697         BOOST_REQUIRE_EQUAL (notes.size(), 0);
698 }
699
700
701 /* DCP with broken SMPTE subtitles */
702 BOOST_AUTO_TEST_CASE (verify_test21)
703 {
704         boost::filesystem::path const dir("build/test/verify_test21");
705         prepare_directory (dir);
706         boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
707         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
708         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
709         shared_ptr<dcp::Reel> reel(new dcp::Reel());
710         reel->add (reel_asset);
711         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
712         cpl->add (reel);
713         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
714         dcp->add (cpl);
715         dcp->write_xml (dcp::SMPTE);
716
717         vector<boost::filesystem::path> dirs;
718         dirs.push_back (dir);
719         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
720         BOOST_REQUIRE_EQUAL (notes.size(), 2);
721         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
722         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
723 }
724
725
726 /* VF */
727 BOOST_AUTO_TEST_CASE (verify_test22)
728 {
729         boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
730         prepare_directory (ov_dir);
731
732         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
733         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
734         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
735         dcp_from_frame (frame, ov_dir);
736
737         dcp::DCP ov (ov_dir);
738         ov.read ();
739
740         boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
741         prepare_directory (vf_dir);
742
743         shared_ptr<dcp::Reel> reel(new dcp::Reel());
744         reel->add (ov.cpls().front()->reels().front()->main_picture());
745         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
746         cpl->add (reel);
747         dcp::DCP vf (vf_dir);
748         vf.add (cpl);
749         vf.write_xml (dcp::SMPTE);
750
751         vector<boost::filesystem::path> dirs;
752         dirs.push_back (vf_dir);
753         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
754         BOOST_REQUIRE_EQUAL (notes.size(), 1);
755         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::EXTERNAL_ASSET);
756 }
757
758
759 /* DCP with valid CompositionMetadataAsset */
760 BOOST_AUTO_TEST_CASE (verify_test23)
761 {
762         boost::filesystem::path const dir("build/test/verify_test23");
763         prepare_directory (dir);
764
765         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
766         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
767         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
768
769         shared_ptr<dcp::Reel> reel(new dcp::Reel());
770         reel->add (reel_asset);
771         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
772         cpl->add (reel);
773         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
774         cpl->set_main_sound_sample_rate (48000);
775         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
776         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
777
778         dcp::DCP dcp (dir);
779         dcp.add (cpl);
780         dcp.write_xml (dcp::SMPTE);
781
782         vector<boost::filesystem::path> dirs;
783         dirs.push_back (dir);
784         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
785 }
786
787
788 boost::filesystem::path find_cpl (boost::filesystem::path dir)
789 {
790         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); i++) {
791                 if (boost::starts_with(i->path().filename().string(), "cpl_")) {
792                         return i->path();
793                 }
794         }
795
796         BOOST_REQUIRE (false);
797         return boost::filesystem::path();
798 }
799
800
801 /* DCP with invalid CompositionMetadataAsset */
802 BOOST_AUTO_TEST_CASE (verify_test24)
803 {
804         boost::filesystem::path const dir("build/test/verify_test24");
805         prepare_directory (dir);
806
807         shared_ptr<dcp::Reel> reel(new dcp::Reel());
808         reel->add (black_picture_asset(dir));
809         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
810         cpl->add (reel);
811         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
812         cpl->set_main_sound_sample_rate (48000);
813         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
814         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
815
816         dcp::DCP dcp (dir);
817         dcp.add (cpl);
818         dcp.write_xml (dcp::SMPTE);
819
820         {
821                 Editor e (find_cpl("build/test/verify_test24"));
822                 e.replace ("MainSound", "MainSoundX");
823         }
824
825         vector<boost::filesystem::path> dirs;
826         dirs.push_back (dir);
827         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
828         BOOST_REQUIRE_EQUAL (notes.size(), 4);
829
830         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
831         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
832         ++i;
833         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
834         ++i;
835         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
836         ++i;
837         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
838         ++i;
839 }
840
841
842 /* DCP with invalid CompositionMetadataAsset */
843 BOOST_AUTO_TEST_CASE (verify_test25)
844 {
845         boost::filesystem::path const dir("build/test/verify_test25");
846         prepare_directory (dir);
847
848         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
849         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
850         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
851
852         shared_ptr<dcp::Reel> reel(new dcp::Reel());
853         reel->add (reel_asset);
854         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
855         cpl->add (reel);
856         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
857         cpl->set_main_sound_sample_rate (48000);
858         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
859         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
860
861         dcp::DCP dcp (dir);
862         dcp.add (cpl);
863         dcp.write_xml (dcp::SMPTE);
864
865         {
866                 Editor e (find_cpl("build/test/verify_test25"));
867                 e.replace ("</MainPictureActiveArea>", "</MainPictureActiveArea><BadTag></BadTag>");
868         }
869
870         vector<boost::filesystem::path> dirs;
871         dirs.push_back (dir);
872         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
873 }
874
875
876 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
877 BOOST_AUTO_TEST_CASE (verify_test26)
878 {
879         boost::filesystem::path const dir("build/test/verify_test26");
880         prepare_directory (dir);
881         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
882         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
883         asset->_language = "wrong-andbad";
884         asset->write (dir / "subs.mxf");
885         shared_ptr<dcp::ReelSubtitleAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
886         reel_asset->_language = "badlang";
887         shared_ptr<dcp::Reel> reel(new dcp::Reel());
888         reel->add (reel_asset);
889         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
890         cpl->add (reel);
891         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
892         dcp->add (cpl);
893         dcp->write_xml (dcp::SMPTE);
894
895         vector<boost::filesystem::path> dirs;
896         dirs.push_back (dir);
897         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
898         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
899         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
900         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
901         BOOST_REQUIRE (i->note());
902         BOOST_CHECK_EQUAL (*i->note(), "badlang");
903         i++;
904         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
905         BOOST_REQUIRE (i->note());
906         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
907 }
908