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