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