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