Bv2.1 7.2.2: Check that subtitle Language tags are present.
[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 "stereo_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "interop_subtitle_asset.h"
47 #include "smpte_subtitle_asset.h"
48 #include "reel_closed_caption_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_subtitle_asset.h"
51 #include "compose.hpp"
52 #include "test.h"
53 #include <boost/test/unit_test.hpp>
54 #include <boost/foreach.hpp>
55 #include <boost/algorithm/string.hpp>
56 #include <cstdio>
57 #include <iostream>
58
59 using std::list;
60 using std::pair;
61 using std::string;
62 using std::vector;
63 using std::make_pair;
64 using boost::optional;
65 using std::shared_ptr;
66
67
68 static list<pair<string, optional<boost::filesystem::path> > > stages;
69
70 static void
71 stage (string s, optional<boost::filesystem::path> p)
72 {
73         stages.push_back (make_pair (s, p));
74 }
75
76 static void
77 progress (float)
78 {
79
80 }
81
82 static void
83 prepare_directory (boost::filesystem::path path)
84 {
85         using namespace boost::filesystem;
86         remove_all (path);
87         create_directories (path);
88 }
89
90
91 static vector<boost::filesystem::path>
92 setup (int reference_number, int verify_test_number)
93 {
94         prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
95         for (boost::filesystem::directory_iterator i(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number)); i != boost::filesystem::directory_iterator(); ++i) {
96                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i->path().filename());
97         }
98
99         vector<boost::filesystem::path> directories;
100         directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
101         return directories;
102
103 }
104
105
106 /** Class that can alter a file by searching and replacing strings within it.
107  *  On destruction modifies the file whose name was given to the constructor.
108  */
109 class Editor
110 {
111 public:
112         Editor (boost::filesystem::path path)
113                 : _path(path)
114         {
115                 _content = dcp::file_to_string (_path);
116         }
117
118         ~Editor ()
119         {
120                 FILE* f = fopen(_path.string().c_str(), "w");
121                 BOOST_REQUIRE (f);
122                 fwrite (_content.c_str(), _content.length(), 1, f);
123                 fclose (f);
124         }
125
126         void replace (string a, string b)
127         {
128                 boost::algorithm::replace_all (_content, a, b);
129         }
130
131 private:
132         boost::filesystem::path _path;
133         std::string _content;
134 };
135
136
137 static
138 void
139 dump_notes (list<dcp::VerificationNote> const & notes)
140 {
141         BOOST_FOREACH (dcp::VerificationNote i, notes) {
142                 std::cout << dcp::note_to_string(i) << "\n";
143         }
144 }
145
146 /* Check DCP as-is (should be OK) */
147 BOOST_AUTO_TEST_CASE (verify_test1)
148 {
149         stages.clear ();
150         vector<boost::filesystem::path> directories = setup (1, 1);
151         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
152
153         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
154         boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml";
155         boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
156
157         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
158         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
159         BOOST_REQUIRE (st->second);
160         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
161         ++st;
162         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
163         BOOST_REQUIRE (st->second);
164         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
165         ++st;
166         BOOST_CHECK_EQUAL (st->first, "Checking reel");
167         BOOST_REQUIRE (!st->second);
168         ++st;
169         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
170         BOOST_REQUIRE (st->second);
171         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
172         ++st;
173         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
174         BOOST_REQUIRE (st->second);
175         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
176         ++st;
177         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
178         BOOST_REQUIRE (st->second);
179         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
180         ++st;
181         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
182         BOOST_REQUIRE (st->second);
183         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
184         ++st;
185         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
186         BOOST_REQUIRE (st->second);
187         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
188         ++st;
189         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
190         BOOST_REQUIRE (st->second);
191         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
192         ++st;
193         BOOST_REQUIRE (st == stages.end());
194
195         BOOST_CHECK_EQUAL (notes.size(), 0);
196 }
197
198 /* Corrupt the MXFs and check that this is spotted */
199 BOOST_AUTO_TEST_CASE (verify_test2)
200 {
201         vector<boost::filesystem::path> directories = setup (1, 2);
202
203         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
204         BOOST_REQUIRE (mod);
205         fseek (mod, 4096, SEEK_SET);
206         int x = 42;
207         fwrite (&x, sizeof(x), 1, mod);
208         fclose (mod);
209
210         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
211         BOOST_REQUIRE (mod);
212         BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
213         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
214         fclose (mod);
215
216         list<dcp::VerificationNote> notes;
217         {
218                 dcp::ASDCPErrorSuspender sus;
219                 notes = dcp::verify (directories, &stage, &progress, xsd_test);
220         }
221
222         BOOST_REQUIRE_EQUAL (notes.size(), 2);
223         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
224         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
225         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
226         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
227 }
228
229 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
230 BOOST_AUTO_TEST_CASE (verify_test3)
231 {
232         vector<boost::filesystem::path> directories = setup (1, 3);
233
234         {
235                 Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml");
236                 e.replace ("<Hash>", "<Hash>x");
237         }
238
239         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
240
241         BOOST_REQUIRE_EQUAL (notes.size(), 6);
242         list<dcp::VerificationNote>::const_iterator i = notes.begin();
243         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
244         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
245         ++i;
246         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
247         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
248         ++i;
249         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
250         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
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         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
259         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
260         ++i;
261 }
262
263 /* Corrupt the ContentKind in the CPL */
264 BOOST_AUTO_TEST_CASE (verify_test4)
265 {
266         vector<boost::filesystem::path> directories = setup (1, 4);
267
268         {
269                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
270                 e.replace ("<ContentKind>", "<ContentKind>x");
271         }
272
273         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
274
275         BOOST_REQUIRE_EQUAL (notes.size(), 1);
276         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
277         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
278 }
279
280 static
281 boost::filesystem::path
282 cpl (int n)
283 {
284         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
285 }
286
287 static
288 boost::filesystem::path
289 pkl (int n)
290 {
291         return dcp::String::compose("build/test/verify_test%1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n);
292 }
293
294 static
295 boost::filesystem::path
296 asset_map (int n)
297 {
298         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
299 }
300
301 static
302 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
303 {
304         vector<boost::filesystem::path> directories = setup (1, n);
305
306         {
307                 Editor e (file(n));
308                 e.replace (from, to);
309         }
310
311         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
312
313         BOOST_REQUIRE_EQUAL (notes.size(), 1);
314         BOOST_CHECK_EQUAL (notes.front().code(), code1);
315 }
316
317 static
318 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)
319 {
320         vector<boost::filesystem::path> directories = setup (1, n);
321
322         {
323                 Editor e (file(n));
324                 e.replace (from, to);
325         }
326
327         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
328
329         BOOST_REQUIRE_EQUAL (notes.size(), 2);
330         BOOST_CHECK_EQUAL (notes.front().code(), code1);
331         BOOST_CHECK_EQUAL (notes.back().code(), code2);
332 }
333
334 static
335 void check_after_replace (
336         int n, boost::function<boost::filesystem::path (int)> file,
337         string from,
338         string to,
339         dcp::VerificationNote::Code code1,
340         dcp::VerificationNote::Code code2,
341         dcp::VerificationNote::Code code3
342         )
343 {
344         vector<boost::filesystem::path> directories = setup (1, n);
345
346         {
347                 Editor e (file(n));
348                 e.replace (from, to);
349         }
350
351         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
352
353         BOOST_REQUIRE_EQUAL (notes.size(), 3);
354         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
355         BOOST_CHECK_EQUAL (i->code(), code1);
356         ++i;
357         BOOST_CHECK_EQUAL (i->code(), code2);
358         ++i;
359         BOOST_CHECK_EQUAL (i->code(), code3);
360 }
361
362 /* FrameRate */
363 BOOST_AUTO_TEST_CASE (verify_test5)
364 {
365         check_after_replace (
366                         5, &cpl,
367                         "<FrameRate>24 1", "<FrameRate>99 1",
368                         dcp::VerificationNote::CPL_HASH_INCORRECT,
369                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
370                         );
371 }
372
373 /* Missing asset */
374 BOOST_AUTO_TEST_CASE (verify_test6)
375 {
376         vector<boost::filesystem::path> directories = setup (1, 6);
377
378         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
379         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
380
381         BOOST_REQUIRE_EQUAL (notes.size(), 1);
382         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
383         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
384 }
385
386 static
387 boost::filesystem::path
388 assetmap (int n)
389 {
390         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
391 }
392
393 /* Empty asset filename in ASSETMAP */
394 BOOST_AUTO_TEST_CASE (verify_test7)
395 {
396         check_after_replace (
397                         7, &assetmap,
398                         "<Path>video.mxf</Path>", "<Path></Path>",
399                         dcp::VerificationNote::EMPTY_ASSET_PATH
400                         );
401 }
402
403 /* Mismatched standard */
404 BOOST_AUTO_TEST_CASE (verify_test8)
405 {
406         check_after_replace (
407                         8, &cpl,
408                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
409                         dcp::VerificationNote::MISMATCHED_STANDARD,
410                         dcp::VerificationNote::XML_VALIDATION_ERROR,
411                         dcp::VerificationNote::CPL_HASH_INCORRECT
412                         );
413 }
414
415 /* Badly formatted <Id> in CPL */
416 BOOST_AUTO_TEST_CASE (verify_test9)
417 {
418         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
419         check_after_replace (
420                         9, &cpl,
421                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
422                         dcp::VerificationNote::XML_VALIDATION_ERROR
423                         );
424 }
425
426 /* Badly formatted <IssueDate> in CPL */
427 BOOST_AUTO_TEST_CASE (verify_test10)
428 {
429         check_after_replace (
430                         10, &cpl,
431                         "<IssueDate>", "<IssueDate>x",
432                         dcp::VerificationNote::XML_VALIDATION_ERROR,
433                         dcp::VerificationNote::CPL_HASH_INCORRECT
434                         );
435 }
436
437 /* Badly-formatted <Id> in PKL */
438 BOOST_AUTO_TEST_CASE (verify_test11)
439 {
440         check_after_replace (
441                 11, &pkl,
442                 "<Id>urn:uuid:cd4", "<Id>urn:uuid:xd4",
443                 dcp::VerificationNote::XML_VALIDATION_ERROR
444                 );
445 }
446
447 /* Badly-formatted <Id> in ASSETMAP */
448 BOOST_AUTO_TEST_CASE (verify_test12)
449 {
450         check_after_replace (
451                 12, &asset_map,
452                 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
453                 dcp::VerificationNote::XML_VALIDATION_ERROR
454                 );
455 }
456
457 /* Basic test of an Interop DCP */
458 BOOST_AUTO_TEST_CASE (verify_test13)
459 {
460         stages.clear ();
461         vector<boost::filesystem::path> directories = setup (3, 13);
462         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
463
464         boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
465         boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
466         boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
467
468         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
469         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
470         BOOST_REQUIRE (st->second);
471         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
472         ++st;
473         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
474         BOOST_REQUIRE (st->second);
475         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
476         ++st;
477         BOOST_CHECK_EQUAL (st->first, "Checking reel");
478         BOOST_REQUIRE (!st->second);
479         ++st;
480         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
481         BOOST_REQUIRE (st->second);
482         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
483         ++st;
484         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
485         BOOST_REQUIRE (st->second);
486         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
487         ++st;
488         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
489         BOOST_REQUIRE (st->second);
490         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
491         ++st;
492         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
493         BOOST_REQUIRE (st->second);
494         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
495         ++st;
496         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
497         BOOST_REQUIRE (st->second);
498         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
499         ++st;
500         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
501         BOOST_REQUIRE (st->second);
502         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
503         ++st;
504         BOOST_REQUIRE (st == stages.end());
505
506         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
507         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
508         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
509         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
510 }
511
512 /* DCP with a short asset */
513 BOOST_AUTO_TEST_CASE (verify_test14)
514 {
515         vector<boost::filesystem::path> directories = setup (8, 14);
516         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
517
518         BOOST_REQUIRE_EQUAL (notes.size(), 5);
519         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
520         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
521         ++i;
522         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
523         ++i;
524         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
525         ++i;
526         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
527         ++i;
528         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
529         ++i;
530 }
531
532
533 static
534 void
535 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
536 {
537         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
538         boost::filesystem::create_directories (dir);
539         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
540         for (int i = 0; i < 24; ++i) {
541                 writer->write (frame.data(), frame.size());
542         }
543         writer->finalize ();
544
545         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelMonoPictureAsset(asset, 0));
546         shared_ptr<dcp::Reel> reel(new dcp::Reel());
547         reel->add (reel_asset);
548         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
549         cpl->add (reel);
550         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
551         dcp->add (cpl);
552         dcp->write_xml (dcp::SMPTE);
553 }
554
555
556 /* DCP with an over-sized JPEG2000 frame */
557 BOOST_AUTO_TEST_CASE (verify_test15)
558 {
559         int const too_big = 1302083 * 2;
560
561         /* Compress a black image */
562         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
563         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
564         BOOST_REQUIRE (frame.size() < too_big);
565
566         /* Place it in a bigger block with some zero padding at the end */
567         dcp::ArrayData oversized_frame(too_big);
568         memcpy (oversized_frame.data(), frame.data(), frame.size());
569         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
570
571         boost::filesystem::path const dir("build/test/verify_test15");
572         boost::filesystem::remove_all (dir);
573         dcp_from_frame (oversized_frame, dir);
574
575         vector<boost::filesystem::path> dirs;
576         dirs.push_back (dir);
577         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
578         BOOST_REQUIRE_EQUAL (notes.size(), 1);
579         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES);
580 }
581
582
583 /* DCP with a nearly over-sized JPEG2000 frame */
584 BOOST_AUTO_TEST_CASE (verify_test16)
585 {
586         int const nearly_too_big = 1302083 * 0.98;
587
588         /* Compress a black image */
589         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
590         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
591         BOOST_REQUIRE (frame.size() < nearly_too_big);
592
593         /* Place it in a bigger block with some zero padding at the end */
594         dcp::ArrayData oversized_frame(nearly_too_big);
595         memcpy (oversized_frame.data(), frame.data(), frame.size());
596         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
597
598         boost::filesystem::path const dir("build/test/verify_test16");
599         boost::filesystem::remove_all (dir);
600         dcp_from_frame (oversized_frame, dir);
601
602         vector<boost::filesystem::path> dirs;
603         dirs.push_back (dir);
604         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
605         BOOST_REQUIRE_EQUAL (notes.size(), 1);
606         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES);
607 }
608
609
610 /* DCP with a within-range JPEG2000 frame */
611 BOOST_AUTO_TEST_CASE (verify_test17)
612 {
613         /* Compress a black image */
614         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
615         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
616         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
617
618         boost::filesystem::path const dir("build/test/verify_test17");
619         boost::filesystem::remove_all (dir);
620         dcp_from_frame (frame, dir);
621
622         vector<boost::filesystem::path> dirs;
623         dirs.push_back (dir);
624         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
625         BOOST_REQUIRE_EQUAL (notes.size(), 0);
626 }
627
628
629 /* DCP with valid Interop subtitles */
630 BOOST_AUTO_TEST_CASE (verify_test18)
631 {
632         boost::filesystem::path const dir("build/test/verify_test18");
633         prepare_directory (dir);
634         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
635         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
636         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
637         shared_ptr<dcp::Reel> reel(new dcp::Reel());
638         reel->add (reel_asset);
639         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
640         cpl->add (reel);
641         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
642         dcp->add (cpl);
643         dcp->write_xml (dcp::INTEROP);
644
645         vector<boost::filesystem::path> dirs;
646         dirs.push_back (dir);
647         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
648         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
649         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
650         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
651 }
652
653
654 /* DCP with broken Interop subtitles */
655 BOOST_AUTO_TEST_CASE (verify_test19)
656 {
657         boost::filesystem::path const dir("build/test/verify_test19");
658         prepare_directory (dir);
659         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
660         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
661         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
662         shared_ptr<dcp::Reel> reel(new dcp::Reel());
663         reel->add (reel_asset);
664         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
665         cpl->add (reel);
666         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
667         dcp->add (cpl);
668         dcp->write_xml (dcp::INTEROP);
669
670         {
671                 Editor e (dir / "subs.xml");
672                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
673         }
674
675         vector<boost::filesystem::path> dirs;
676         dirs.push_back (dir);
677         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
678         BOOST_REQUIRE_EQUAL (notes.size(), 3);
679         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
680         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
681         ++i;
682         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
683         ++i;
684         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
685         ++i;
686 }
687
688
689 /* DCP with valid SMPTE subtitles */
690 BOOST_AUTO_TEST_CASE (verify_test20)
691 {
692         boost::filesystem::path const dir("build/test/verify_test20");
693         prepare_directory (dir);
694         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
695         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
696         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
697         shared_ptr<dcp::Reel> reel(new dcp::Reel());
698         reel->add (reel_asset);
699         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
700         cpl->add (reel);
701         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
702         dcp->add (cpl);
703         dcp->write_xml (dcp::SMPTE);
704
705         vector<boost::filesystem::path> dirs;
706         dirs.push_back (dir);
707         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
708         dump_notes (notes);
709         BOOST_REQUIRE_EQUAL (notes.size(), 0);
710 }
711
712
713 /* DCP with broken SMPTE subtitles */
714 BOOST_AUTO_TEST_CASE (verify_test21)
715 {
716         boost::filesystem::path const dir("build/test/verify_test21");
717         prepare_directory (dir);
718         boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
719         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
720         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
721         shared_ptr<dcp::Reel> reel(new dcp::Reel());
722         reel->add (reel_asset);
723         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
724         cpl->add (reel);
725         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
726         dcp->add (cpl);
727         dcp->write_xml (dcp::SMPTE);
728
729         vector<boost::filesystem::path> dirs;
730         dirs.push_back (dir);
731         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
732         BOOST_REQUIRE_EQUAL (notes.size(), 2);
733         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
734         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
735 }
736
737
738 /* VF */
739 BOOST_AUTO_TEST_CASE (verify_test22)
740 {
741         boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
742         prepare_directory (ov_dir);
743
744         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
745         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
746         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
747         dcp_from_frame (frame, ov_dir);
748
749         dcp::DCP ov (ov_dir);
750         ov.read ();
751
752         boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
753         prepare_directory (vf_dir);
754
755         shared_ptr<dcp::Reel> reel(new dcp::Reel());
756         reel->add (ov.cpls().front()->reels().front()->main_picture());
757         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
758         cpl->add (reel);
759         dcp::DCP vf (vf_dir);
760         vf.add (cpl);
761         vf.write_xml (dcp::SMPTE);
762
763         vector<boost::filesystem::path> dirs;
764         dirs.push_back (vf_dir);
765         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
766         BOOST_REQUIRE_EQUAL (notes.size(), 1);
767         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::EXTERNAL_ASSET);
768 }
769
770
771 /* DCP with valid CompositionMetadataAsset */
772 BOOST_AUTO_TEST_CASE (verify_test23)
773 {
774         boost::filesystem::path const dir("build/test/verify_test23");
775         prepare_directory (dir);
776
777         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
778         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
779         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
780
781         shared_ptr<dcp::Reel> reel(new dcp::Reel());
782         reel->add (reel_asset);
783         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
784         cpl->add (reel);
785         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
786         cpl->set_main_sound_sample_rate (48000);
787         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
788         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
789
790         dcp::DCP dcp (dir);
791         dcp.add (cpl);
792         dcp.write_xml (dcp::SMPTE);
793
794         vector<boost::filesystem::path> dirs;
795         dirs.push_back (dir);
796         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
797 }
798
799
800 boost::filesystem::path find_cpl (boost::filesystem::path dir)
801 {
802         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); i++) {
803                 if (boost::starts_with(i->path().filename().string(), "cpl_")) {
804                         return i->path();
805                 }
806         }
807
808         BOOST_REQUIRE (false);
809         return boost::filesystem::path();
810 }
811
812
813 /* DCP with invalid CompositionMetadataAsset */
814 BOOST_AUTO_TEST_CASE (verify_test24)
815 {
816         boost::filesystem::path const dir("build/test/verify_test24");
817         prepare_directory (dir);
818
819         shared_ptr<dcp::Reel> reel(new dcp::Reel());
820         reel->add (black_picture_asset(dir));
821         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
822         cpl->add (reel);
823         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
824         cpl->set_main_sound_sample_rate (48000);
825         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
826         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
827
828         dcp::DCP dcp (dir);
829         dcp.add (cpl);
830         dcp.write_xml (dcp::SMPTE);
831
832         {
833                 Editor e (find_cpl("build/test/verify_test24"));
834                 e.replace ("MainSound", "MainSoundX");
835         }
836
837         vector<boost::filesystem::path> dirs;
838         dirs.push_back (dir);
839         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
840         BOOST_REQUIRE_EQUAL (notes.size(), 4);
841
842         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
843         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
844         ++i;
845         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
846         ++i;
847         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
848         ++i;
849         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
850         ++i;
851 }
852
853
854 /* DCP with invalid CompositionMetadataAsset */
855 BOOST_AUTO_TEST_CASE (verify_test25)
856 {
857         boost::filesystem::path const dir("build/test/verify_test25");
858         prepare_directory (dir);
859
860         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
861         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
862         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
863
864         shared_ptr<dcp::Reel> reel(new dcp::Reel());
865         reel->add (reel_asset);
866         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
867         cpl->add (reel);
868         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
869         cpl->set_main_sound_sample_rate (48000);
870         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
871         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
872
873         dcp::DCP dcp (dir);
874         dcp.add (cpl);
875         dcp.write_xml (dcp::SMPTE);
876
877         {
878                 Editor e (find_cpl("build/test/verify_test25"));
879                 e.replace ("</MainPictureActiveArea>", "</MainPictureActiveArea><BadTag></BadTag>");
880         }
881
882         vector<boost::filesystem::path> dirs;
883         dirs.push_back (dir);
884         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
885 }
886
887
888 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
889 BOOST_AUTO_TEST_CASE (verify_test26)
890 {
891         boost::filesystem::path const dir("build/test/verify_test26");
892         prepare_directory (dir);
893         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
894         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
895         asset->_language = "wrong-andbad";
896         asset->write (dir / "subs.mxf");
897         shared_ptr<dcp::ReelSubtitleAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
898         reel_asset->_language = "badlang";
899         shared_ptr<dcp::Reel> reel(new dcp::Reel());
900         reel->add (reel_asset);
901         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
902         cpl->add (reel);
903         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
904         dcp->add (cpl);
905         dcp->write_xml (dcp::SMPTE);
906
907         vector<boost::filesystem::path> dirs;
908         dirs.push_back (dir);
909         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
910         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
911         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
912         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
913         BOOST_REQUIRE (i->note());
914         BOOST_CHECK_EQUAL (*i->note(), "badlang");
915         i++;
916         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
917         BOOST_REQUIRE (i->note());
918         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
919 }
920
921
922 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
923 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
924 {
925         boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages");
926         prepare_directory (dir);
927         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
928         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
929         asset->_language = "wrong-andbad";
930         asset->write (dir / "subs.mxf");
931         shared_ptr<dcp::ReelClosedCaptionAsset> reel_asset(new dcp::ReelClosedCaptionAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
932         reel_asset->_language = "badlang";
933         shared_ptr<dcp::Reel> reel(new dcp::Reel());
934         reel->add (reel_asset);
935         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
936         cpl->add (reel);
937         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
938         dcp->add (cpl);
939         dcp->write_xml (dcp::SMPTE);
940
941         vector<boost::filesystem::path> dirs;
942         dirs.push_back (dir);
943         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
944         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
945         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
946         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
947         BOOST_REQUIRE (i->note());
948         BOOST_CHECK_EQUAL (*i->note(), "badlang");
949         i++;
950         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
951         BOOST_REQUIRE (i->note());
952         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
953 }
954
955
956 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
957  * the release territory.
958  */
959 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
960 {
961         boost::filesystem::path const dir("build/test/verify_various_invalid_languages");
962         prepare_directory (dir);
963
964         shared_ptr<dcp::MonoPictureAsset> picture = simple_picture (dir, "foo");
965         shared_ptr<dcp::ReelPictureAsset> reel_picture(new dcp::ReelMonoPictureAsset(picture, 0));
966         shared_ptr<dcp::Reel> reel(new dcp::Reel());
967         reel->add (reel_picture);
968         shared_ptr<dcp::SoundAsset> sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
969         shared_ptr<dcp::ReelSoundAsset> reel_sound(new dcp::ReelSoundAsset(sound, 0));
970         reel->add (reel_sound);
971         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
972         cpl->add (reel);
973         cpl->_additional_subtitle_languages.push_back("this-is-wrong");
974         cpl->_additional_subtitle_languages.push_back("andso-is-this");
975         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
976         cpl->set_main_sound_sample_rate (48000);
977         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
978         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
979         cpl->_release_territory = "fred-jim";
980         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
981         dcp->add (cpl);
982         dcp->write_xml (dcp::SMPTE);
983
984         vector<boost::filesystem::path> dirs;
985         dirs.push_back (dir);
986         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
987         BOOST_REQUIRE_EQUAL (notes.size(), 4U);
988         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
989         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
990         BOOST_REQUIRE (i->note());
991         BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
992         ++i;
993         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
994         BOOST_REQUIRE (i->note());
995         BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
996         ++i;
997         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
998         BOOST_REQUIRE (i->note());
999         BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
1000         ++i;
1001         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
1002         BOOST_REQUIRE (i->note());
1003         BOOST_CHECK_EQUAL (*i->note(), "frobozz");
1004         ++i;
1005 }
1006
1007
1008 static
1009 list<dcp::VerificationNote>
1010 check_picture_size (int width, int height, int frame_rate, bool three_d)
1011 {
1012         using namespace boost::filesystem;
1013
1014         path dcp_path = "build/test/verify_picture_test";
1015         remove_all (dcp_path);
1016         create_directories (dcp_path);
1017
1018         shared_ptr<dcp::PictureAsset> mp;
1019         if (three_d) {
1020                 mp.reset (new dcp::StereoPictureAsset(dcp::Fraction(frame_rate, 1), dcp::SMPTE));
1021         } else {
1022                 mp.reset (new dcp::MonoPictureAsset(dcp::Fraction(frame_rate, 1), dcp::SMPTE));
1023         }
1024         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1025
1026         shared_ptr<dcp::OpenJPEGImage> image = black_image (dcp::Size(width, height));
1027         dcp::ArrayData j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1028         int const length = three_d ? frame_rate * 2 : frame_rate;
1029         for (int i = 0; i < length; ++i) {
1030                 picture_writer->write (j2c.data(), j2c.size());
1031         }
1032         picture_writer->finalize ();
1033
1034         shared_ptr<dcp::DCP> d (new dcp::DCP(dcp_path));
1035         shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::FEATURE));
1036         cpl->set_annotation_text ("A Test DCP");
1037         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1038
1039         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1040
1041         if (three_d) {
1042                 reel->add (
1043                         shared_ptr<dcp::ReelPictureAsset>(
1044                                 new dcp::ReelStereoPictureAsset(
1045                                         std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp),
1046                                         0)
1047                                 )
1048                         );
1049         } else {
1050                 reel->add (
1051                         shared_ptr<dcp::ReelPictureAsset>(
1052                                 new dcp::ReelMonoPictureAsset(
1053                                         std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp),
1054                                         0)
1055                                 )
1056                         );
1057         }
1058
1059         cpl->add (reel);
1060
1061         d->add (cpl);
1062         d->write_xml (dcp::SMPTE);
1063
1064         vector<boost::filesystem::path> dirs;
1065         dirs.push_back (dcp_path);
1066         return dcp::verify (dirs, &stage, &progress, xsd_test);
1067 }
1068
1069
1070 static
1071 void
1072 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1073 {
1074         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1075         dump_notes (notes);
1076         BOOST_CHECK_EQUAL (notes.size(), 0U);
1077 }
1078
1079
1080 static
1081 void
1082 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1083 {
1084         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1085         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1086         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1087         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
1088 }
1089
1090
1091 static
1092 void
1093 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1094 {
1095         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1096         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1097         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1098         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
1099 }
1100
1101
1102 static
1103 void
1104 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1105 {
1106         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1107         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1108         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1109         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
1110 }
1111
1112
1113 BOOST_AUTO_TEST_CASE (verify_picture_size)
1114 {
1115         using namespace boost::filesystem;
1116
1117         /* 2K scope */
1118         check_picture_size_ok (2048, 858, 24, false);
1119         check_picture_size_ok (2048, 858, 25, false);
1120         check_picture_size_ok (2048, 858, 48, false);
1121         check_picture_size_ok (2048, 858, 24, true);
1122         check_picture_size_ok (2048, 858, 25, true);
1123         check_picture_size_ok (2048, 858, 48, true);
1124
1125         /* 2K flat */
1126         check_picture_size_ok (1998, 1080, 24, false);
1127         check_picture_size_ok (1998, 1080, 25, false);
1128         check_picture_size_ok (1998, 1080, 48, false);
1129         check_picture_size_ok (1998, 1080, 24, true);
1130         check_picture_size_ok (1998, 1080, 25, true);
1131         check_picture_size_ok (1998, 1080, 48, true);
1132
1133         /* 4K scope */
1134         check_picture_size_ok (4096, 1716, 24, false);
1135
1136         /* 4K flat */
1137         check_picture_size_ok (3996, 2160, 24, false);
1138
1139         /* Bad frame size */
1140         check_picture_size_bad_frame_size (2050, 858, 24, false);
1141         check_picture_size_bad_frame_size (2048, 658, 25, false);
1142         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1143         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1144
1145         /* Bad 2K frame rate */
1146         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1147         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1148         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1149
1150         /* Bad 4K frame rate */
1151         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1152         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1153
1154         /* No 4K 3D */
1155         list<dcp::VerificationNote> notes = check_picture_size(3996, 2160, 24, true);
1156         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1157         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1158         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1159 }
1160
1161
1162 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1163 {
1164         boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1165         prepare_directory (dir);
1166
1167         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset());
1168         for (int i = 0; i < 2048; ++i) {
1169                 asset->add (
1170                         shared_ptr<dcp::Subtitle>(
1171                                 new dcp::SubtitleString(
1172                                         optional<string>(),
1173                                         false,
1174                                         false,
1175                                         false,
1176                                         dcp::Colour(),
1177                                         42,
1178                                         1,
1179                                         dcp::Time(i * 24, 24, 24),
1180                                         dcp::Time(i * 24 + 20, 24, 24),
1181                                         0,
1182                                         dcp::HALIGN_CENTER,
1183                                         0,
1184                                         dcp::VALIGN_CENTER,
1185                                         dcp::DIRECTION_LTR,
1186                                         "Hello",
1187                                         dcp::NONE,
1188                                         dcp::Colour(),
1189                                         dcp::Time(),
1190                                         dcp::Time()
1191                                         )
1192                                 )
1193                         );
1194         }
1195         asset->set_language (dcp::LanguageTag("de-DE"));
1196         asset->write (dir / "subs.mxf");
1197         shared_ptr<dcp::ReelClosedCaptionAsset> reel_asset(new dcp::ReelClosedCaptionAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
1198         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1199         reel->add (reel_asset);
1200         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
1201         cpl->add (reel);
1202         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
1203         dcp->add (cpl);
1204         dcp->write_xml (dcp::SMPTE);
1205
1206         vector<boost::filesystem::path> dirs;
1207         dirs.push_back (dir);
1208         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1209         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1210         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1211         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES);
1212 }
1213
1214
1215 static
1216 shared_ptr<dcp::SMPTESubtitleAsset>
1217 make_large_subtitle_asset (boost::filesystem::path font_file)
1218 {
1219         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset());
1220         dcp::ArrayData big_fake_font(1024 * 1024);
1221         big_fake_font.write (font_file);
1222         for (int i = 0; i < 116; ++i) {
1223                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1224         }
1225         return asset;
1226 }
1227
1228
1229 template <class T>
1230 void
1231 verify_timed_text_asset_too_large (string name)
1232 {
1233         boost::filesystem::path const dir = boost::filesystem::path("build/test") / name;
1234         prepare_directory (dir);
1235         shared_ptr<dcp::SMPTESubtitleAsset> asset = make_large_subtitle_asset (dir / "font.ttf");
1236         asset->add (
1237                 shared_ptr<dcp::Subtitle>(
1238                         new dcp::SubtitleString(
1239                                 optional<string>(),
1240                                 false,
1241                                 false,
1242                                 false,
1243                                 dcp::Colour(),
1244                                 42,
1245                                 1,
1246                                 dcp::Time(0, 24, 24),
1247                                 dcp::Time(20, 24, 24),
1248                                 0,
1249                                 dcp::HALIGN_CENTER,
1250                                 0,
1251                                 dcp::VALIGN_CENTER,
1252                                 dcp::DIRECTION_LTR,
1253                                 "Hello",
1254                                 dcp::NONE,
1255                                 dcp::Colour(),
1256                                 dcp::Time(),
1257                                 dcp::Time()
1258                                 )
1259                         )
1260                 );
1261         asset->set_language (dcp::LanguageTag("de-DE"));
1262         asset->write (dir / "subs.mxf");
1263
1264         shared_ptr<T> reel_asset(new T(asset, dcp::Fraction(24, 1), 16 * 24, 0));
1265         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1266         reel->add (reel_asset);
1267         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
1268         cpl->add (reel);
1269         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
1270         dcp->add (cpl);
1271         dcp->write_xml (dcp::SMPTE);
1272
1273         vector<boost::filesystem::path> dirs;
1274         dirs.push_back (dir);
1275         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1276         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1277         list<dcp::VerificationNote>::const_iterator i = notes.begin();
1278         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1279         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES);
1280         ++i;
1281         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1282         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES);
1283 }
1284
1285
1286 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1287 {
1288         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1289         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1290 }
1291
1292
1293 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1294 {
1295         boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1296         prepare_directory (dir);
1297         shared_ptr<dcp::DCP> dcp = make_simple (dir, 1);
1298
1299         string const xml =
1300                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1301                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1302                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1303                 "<ContentTitleText>Content</ContentTitleText>"
1304                 "<AnnotationText>Annotation</AnnotationText>"
1305                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1306                 "<ReelNumber>1</ReelNumber>"
1307                 "<EditRate>25 1</EditRate>"
1308                 "<TimeCodeRate>25</TimeCodeRate>"
1309                 "<StartTime>00:00:00:00</StartTime>"
1310                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1311                 "<SubtitleList>"
1312                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1313                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1314                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1315                 "</Subtitle>"
1316                 "</Font>"
1317                 "</SubtitleList>"
1318                 "</SubtitleReel>";
1319
1320         FILE* xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1321         BOOST_REQUIRE (xml_file);
1322         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1323         fclose (xml_file);
1324         shared_ptr<dcp::SMPTESubtitleAsset> subs (new dcp::SMPTESubtitleAsset(dir / "subs.xml"));
1325         subs->write (dir / "subs.mxf");
1326
1327         shared_ptr<dcp::ReelSubtitleAsset> reel_subs (new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 100, 0));
1328         dcp->cpls().front()->reels().front()->add (reel_subs);
1329         dcp->write_xml (dcp::SMPTE);
1330
1331         vector<boost::filesystem::path> dirs;
1332         dirs.push_back (dir);
1333         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1334         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1335         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1336         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE);
1337 }