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