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