Bv2.1 7.2.3: Check that subtitle <StartTime> exists and is 0.
[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_DIFFER);
248         ++i;
249         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
250         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER);
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(), 3);
733         list<dcp::VerificationNote>::const_iterator i = notes.begin();
734         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
735         ++i;
736         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
737         ++i;
738         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
739 }
740
741
742 /* VF */
743 BOOST_AUTO_TEST_CASE (verify_test22)
744 {
745         boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
746         prepare_directory (ov_dir);
747
748         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
749         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
750         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
751         dcp_from_frame (frame, ov_dir);
752
753         dcp::DCP ov (ov_dir);
754         ov.read ();
755
756         boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
757         prepare_directory (vf_dir);
758
759         shared_ptr<dcp::Reel> reel(new dcp::Reel());
760         reel->add (ov.cpls().front()->reels().front()->main_picture());
761         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
762         cpl->add (reel);
763         dcp::DCP vf (vf_dir);
764         vf.add (cpl);
765         vf.write_xml (dcp::SMPTE);
766
767         vector<boost::filesystem::path> dirs;
768         dirs.push_back (vf_dir);
769         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
770         BOOST_REQUIRE_EQUAL (notes.size(), 1);
771         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::EXTERNAL_ASSET);
772 }
773
774
775 /* DCP with valid CompositionMetadataAsset */
776 BOOST_AUTO_TEST_CASE (verify_test23)
777 {
778         boost::filesystem::path const dir("build/test/verify_test23");
779         prepare_directory (dir);
780
781         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
782         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
783         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
784
785         shared_ptr<dcp::Reel> reel(new dcp::Reel());
786         reel->add (reel_asset);
787         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
788         cpl->add (reel);
789         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
790         cpl->set_main_sound_sample_rate (48000);
791         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
792         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
793
794         dcp::DCP dcp (dir);
795         dcp.add (cpl);
796         dcp.write_xml (dcp::SMPTE);
797
798         vector<boost::filesystem::path> dirs;
799         dirs.push_back (dir);
800         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
801 }
802
803
804 boost::filesystem::path find_cpl (boost::filesystem::path dir)
805 {
806         for (boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator(dir); i != boost::filesystem::directory_iterator(); i++) {
807                 if (boost::starts_with(i->path().filename().string(), "cpl_")) {
808                         return i->path();
809                 }
810         }
811
812         BOOST_REQUIRE (false);
813         return boost::filesystem::path();
814 }
815
816
817 /* DCP with invalid CompositionMetadataAsset */
818 BOOST_AUTO_TEST_CASE (verify_test24)
819 {
820         boost::filesystem::path const dir("build/test/verify_test24");
821         prepare_directory (dir);
822
823         shared_ptr<dcp::Reel> reel(new dcp::Reel());
824         reel->add (black_picture_asset(dir));
825         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
826         cpl->add (reel);
827         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
828         cpl->set_main_sound_sample_rate (48000);
829         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
830         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
831
832         dcp::DCP dcp (dir);
833         dcp.add (cpl);
834         dcp.write_xml (dcp::SMPTE);
835
836         {
837                 Editor e (find_cpl("build/test/verify_test24"));
838                 e.replace ("MainSound", "MainSoundX");
839         }
840
841         vector<boost::filesystem::path> dirs;
842         dirs.push_back (dir);
843         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
844         BOOST_REQUIRE_EQUAL (notes.size(), 4);
845
846         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
847         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
848         ++i;
849         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
850         ++i;
851         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
852         ++i;
853         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
854         ++i;
855 }
856
857
858 /* DCP with invalid CompositionMetadataAsset */
859 BOOST_AUTO_TEST_CASE (verify_test25)
860 {
861         boost::filesystem::path const dir("build/test/verify_test25");
862         prepare_directory (dir);
863
864         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
865         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
866         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
867
868         shared_ptr<dcp::Reel> reel(new dcp::Reel());
869         reel->add (reel_asset);
870         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
871         cpl->add (reel);
872         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873         cpl->set_main_sound_sample_rate (48000);
874         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
876
877         dcp::DCP dcp (dir);
878         dcp.add (cpl);
879         dcp.write_xml (dcp::SMPTE);
880
881         {
882                 Editor e (find_cpl("build/test/verify_test25"));
883                 e.replace ("</MainPictureActiveArea>", "</MainPictureActiveArea><BadTag></BadTag>");
884         }
885
886         vector<boost::filesystem::path> dirs;
887         dirs.push_back (dir);
888         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
889 }
890
891
892 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
893 BOOST_AUTO_TEST_CASE (verify_test26)
894 {
895         boost::filesystem::path const dir("build/test/verify_test26");
896         prepare_directory (dir);
897         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
898         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
899         asset->_language = "wrong-andbad";
900         asset->write (dir / "subs.mxf");
901         shared_ptr<dcp::ReelSubtitleAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
902         reel_asset->_language = "badlang";
903         shared_ptr<dcp::Reel> reel(new dcp::Reel());
904         reel->add (reel_asset);
905         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
906         cpl->add (reel);
907         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
908         dcp->add (cpl);
909         dcp->write_xml (dcp::SMPTE);
910
911         vector<boost::filesystem::path> dirs;
912         dirs.push_back (dir);
913         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
914         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
915         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
916         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
917         BOOST_REQUIRE (i->note());
918         BOOST_CHECK_EQUAL (*i->note(), "badlang");
919         i++;
920         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
921         BOOST_REQUIRE (i->note());
922         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
923 }
924
925
926 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
927 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
928 {
929         boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages");
930         prepare_directory (dir);
931         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
932         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
933         asset->_language = "wrong-andbad";
934         asset->write (dir / "subs.mxf");
935         shared_ptr<dcp::ReelClosedCaptionAsset> reel_asset(new dcp::ReelClosedCaptionAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
936         reel_asset->_language = "badlang";
937         shared_ptr<dcp::Reel> reel(new dcp::Reel());
938         reel->add (reel_asset);
939         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
940         cpl->add (reel);
941         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
942         dcp->add (cpl);
943         dcp->write_xml (dcp::SMPTE);
944
945         vector<boost::filesystem::path> dirs;
946         dirs.push_back (dir);
947         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
948         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
949         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
950         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
951         BOOST_REQUIRE (i->note());
952         BOOST_CHECK_EQUAL (*i->note(), "badlang");
953         i++;
954         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
955         BOOST_REQUIRE (i->note());
956         BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
957 }
958
959
960 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
961  * the release territory.
962  */
963 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
964 {
965         boost::filesystem::path const dir("build/test/verify_various_invalid_languages");
966         prepare_directory (dir);
967
968         shared_ptr<dcp::MonoPictureAsset> picture = simple_picture (dir, "foo");
969         shared_ptr<dcp::ReelPictureAsset> reel_picture(new dcp::ReelMonoPictureAsset(picture, 0));
970         shared_ptr<dcp::Reel> reel(new dcp::Reel());
971         reel->add (reel_picture);
972         shared_ptr<dcp::SoundAsset> sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
973         shared_ptr<dcp::ReelSoundAsset> reel_sound(new dcp::ReelSoundAsset(sound, 0));
974         reel->add (reel_sound);
975         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
976         cpl->add (reel);
977         cpl->_additional_subtitle_languages.push_back("this-is-wrong");
978         cpl->_additional_subtitle_languages.push_back("andso-is-this");
979         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
980         cpl->set_main_sound_sample_rate (48000);
981         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
982         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
983         cpl->_release_territory = "fred-jim";
984         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
985         dcp->add (cpl);
986         dcp->write_xml (dcp::SMPTE);
987
988         vector<boost::filesystem::path> dirs;
989         dirs.push_back (dir);
990         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
991         BOOST_REQUIRE_EQUAL (notes.size(), 4U);
992         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
993         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
994         BOOST_REQUIRE (i->note());
995         BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
996         ++i;
997         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
998         BOOST_REQUIRE (i->note());
999         BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
1000         ++i;
1001         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
1002         BOOST_REQUIRE (i->note());
1003         BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
1004         ++i;
1005         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
1006         BOOST_REQUIRE (i->note());
1007         BOOST_CHECK_EQUAL (*i->note(), "frobozz");
1008         ++i;
1009 }
1010
1011
1012 static
1013 list<dcp::VerificationNote>
1014 check_picture_size (int width, int height, int frame_rate, bool three_d)
1015 {
1016         using namespace boost::filesystem;
1017
1018         path dcp_path = "build/test/verify_picture_test";
1019         remove_all (dcp_path);
1020         create_directories (dcp_path);
1021
1022         shared_ptr<dcp::PictureAsset> mp;
1023         if (three_d) {
1024                 mp.reset (new dcp::StereoPictureAsset(dcp::Fraction(frame_rate, 1), dcp::SMPTE));
1025         } else {
1026                 mp.reset (new dcp::MonoPictureAsset(dcp::Fraction(frame_rate, 1), dcp::SMPTE));
1027         }
1028         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1029
1030         shared_ptr<dcp::OpenJPEGImage> image = black_image (dcp::Size(width, height));
1031         dcp::ArrayData j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1032         int const length = three_d ? frame_rate * 2 : frame_rate;
1033         for (int i = 0; i < length; ++i) {
1034                 picture_writer->write (j2c.data(), j2c.size());
1035         }
1036         picture_writer->finalize ();
1037
1038         shared_ptr<dcp::DCP> d (new dcp::DCP(dcp_path));
1039         shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::FEATURE));
1040         cpl->set_annotation_text ("A Test DCP");
1041         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1042
1043         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1044
1045         if (three_d) {
1046                 reel->add (
1047                         shared_ptr<dcp::ReelPictureAsset>(
1048                                 new dcp::ReelStereoPictureAsset(
1049                                         std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp),
1050                                         0)
1051                                 )
1052                         );
1053         } else {
1054                 reel->add (
1055                         shared_ptr<dcp::ReelPictureAsset>(
1056                                 new dcp::ReelMonoPictureAsset(
1057                                         std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp),
1058                                         0)
1059                                 )
1060                         );
1061         }
1062
1063         cpl->add (reel);
1064
1065         d->add (cpl);
1066         d->write_xml (dcp::SMPTE);
1067
1068         vector<boost::filesystem::path> dirs;
1069         dirs.push_back (dcp_path);
1070         return dcp::verify (dirs, &stage, &progress, xsd_test);
1071 }
1072
1073
1074 static
1075 void
1076 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1077 {
1078         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1079         dump_notes (notes);
1080         BOOST_CHECK_EQUAL (notes.size(), 0U);
1081 }
1082
1083
1084 static
1085 void
1086 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1087 {
1088         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1089         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1090         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1091         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
1092 }
1093
1094
1095 static
1096 void
1097 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1098 {
1099         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1100         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1101         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1102         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
1103 }
1104
1105
1106 static
1107 void
1108 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1109 {
1110         list<dcp::VerificationNote> notes = check_picture_size(width, height, frame_rate, three_d);
1111         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1112         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1113         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
1114 }
1115
1116
1117 BOOST_AUTO_TEST_CASE (verify_picture_size)
1118 {
1119         using namespace boost::filesystem;
1120
1121         /* 2K scope */
1122         check_picture_size_ok (2048, 858, 24, false);
1123         check_picture_size_ok (2048, 858, 25, false);
1124         check_picture_size_ok (2048, 858, 48, false);
1125         check_picture_size_ok (2048, 858, 24, true);
1126         check_picture_size_ok (2048, 858, 25, true);
1127         check_picture_size_ok (2048, 858, 48, true);
1128
1129         /* 2K flat */
1130         check_picture_size_ok (1998, 1080, 24, false);
1131         check_picture_size_ok (1998, 1080, 25, false);
1132         check_picture_size_ok (1998, 1080, 48, false);
1133         check_picture_size_ok (1998, 1080, 24, true);
1134         check_picture_size_ok (1998, 1080, 25, true);
1135         check_picture_size_ok (1998, 1080, 48, true);
1136
1137         /* 4K scope */
1138         check_picture_size_ok (4096, 1716, 24, false);
1139
1140         /* 4K flat */
1141         check_picture_size_ok (3996, 2160, 24, false);
1142
1143         /* Bad frame size */
1144         check_picture_size_bad_frame_size (2050, 858, 24, false);
1145         check_picture_size_bad_frame_size (2048, 658, 25, false);
1146         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1147         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1148
1149         /* Bad 2K frame rate */
1150         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1151         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1152         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1153
1154         /* Bad 4K frame rate */
1155         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1156         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1157
1158         /* No 4K 3D */
1159         list<dcp::VerificationNote> notes = check_picture_size(3996, 2160, 24, true);
1160         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1161         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1162         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1163 }
1164
1165
1166 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1167 {
1168         boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1169         prepare_directory (dir);
1170
1171         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset());
1172         for (int i = 0; i < 2048; ++i) {
1173                 asset->add (
1174                         shared_ptr<dcp::Subtitle>(
1175                                 new dcp::SubtitleString(
1176                                         optional<string>(),
1177                                         false,
1178                                         false,
1179                                         false,
1180                                         dcp::Colour(),
1181                                         42,
1182                                         1,
1183                                         dcp::Time(i * 24, 24, 24),
1184                                         dcp::Time(i * 24 + 20, 24, 24),
1185                                         0,
1186                                         dcp::HALIGN_CENTER,
1187                                         0,
1188                                         dcp::VALIGN_CENTER,
1189                                         dcp::DIRECTION_LTR,
1190                                         "Hello",
1191                                         dcp::NONE,
1192                                         dcp::Colour(),
1193                                         dcp::Time(),
1194                                         dcp::Time()
1195                                         )
1196                                 )
1197                         );
1198         }
1199         asset->set_language (dcp::LanguageTag("de-DE"));
1200         asset->write (dir / "subs.mxf");
1201         shared_ptr<dcp::ReelClosedCaptionAsset> reel_asset(new dcp::ReelClosedCaptionAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
1202         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1203         reel->add (reel_asset);
1204         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
1205         cpl->add (reel);
1206         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
1207         dcp->add (cpl);
1208         dcp->write_xml (dcp::SMPTE);
1209
1210         vector<boost::filesystem::path> dirs;
1211         dirs.push_back (dir);
1212         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1213         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1214         list<dcp::VerificationNote>::const_iterator i = notes.begin();
1215         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
1216         ++i;
1217         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1218         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES);
1219 }
1220
1221
1222 static
1223 shared_ptr<dcp::SMPTESubtitleAsset>
1224 make_large_subtitle_asset (boost::filesystem::path font_file)
1225 {
1226         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset());
1227         dcp::ArrayData big_fake_font(1024 * 1024);
1228         big_fake_font.write (font_file);
1229         for (int i = 0; i < 116; ++i) {
1230                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1231         }
1232         return asset;
1233 }
1234
1235
1236 template <class T>
1237 void
1238 verify_timed_text_asset_too_large (string name)
1239 {
1240         boost::filesystem::path const dir = boost::filesystem::path("build/test") / name;
1241         prepare_directory (dir);
1242         shared_ptr<dcp::SMPTESubtitleAsset> asset = make_large_subtitle_asset (dir / "font.ttf");
1243         asset->add (
1244                 shared_ptr<dcp::Subtitle>(
1245                         new dcp::SubtitleString(
1246                                 optional<string>(),
1247                                 false,
1248                                 false,
1249                                 false,
1250                                 dcp::Colour(),
1251                                 42,
1252                                 1,
1253                                 dcp::Time(0, 24, 24),
1254                                 dcp::Time(20, 24, 24),
1255                                 0,
1256                                 dcp::HALIGN_CENTER,
1257                                 0,
1258                                 dcp::VALIGN_CENTER,
1259                                 dcp::DIRECTION_LTR,
1260                                 "Hello",
1261                                 dcp::NONE,
1262                                 dcp::Colour(),
1263                                 dcp::Time(),
1264                                 dcp::Time()
1265                                 )
1266                         )
1267                 );
1268         asset->set_language (dcp::LanguageTag("de-DE"));
1269         asset->write (dir / "subs.mxf");
1270
1271         shared_ptr<T> reel_asset(new T(asset, dcp::Fraction(24, 1), 16 * 24, 0));
1272         shared_ptr<dcp::Reel> reel(new dcp::Reel());
1273         reel->add (reel_asset);
1274         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
1275         cpl->add (reel);
1276         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
1277         dcp->add (cpl);
1278         dcp->write_xml (dcp::SMPTE);
1279
1280         vector<boost::filesystem::path> dirs;
1281         dirs.push_back (dir);
1282         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1283         BOOST_REQUIRE_EQUAL (notes.size(), 3U);
1284         list<dcp::VerificationNote>::const_iterator i = notes.begin();
1285         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1286         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES);
1287         ++i;
1288         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1289         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES);
1290         ++i;
1291         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
1292 }
1293
1294
1295 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1296 {
1297         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1298         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1299 }
1300
1301
1302 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1303 {
1304         boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1305         prepare_directory (dir);
1306         shared_ptr<dcp::DCP> dcp = make_simple (dir, 1);
1307
1308         string const xml =
1309                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1310                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1311                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1312                 "<ContentTitleText>Content</ContentTitleText>"
1313                 "<AnnotationText>Annotation</AnnotationText>"
1314                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1315                 "<ReelNumber>1</ReelNumber>"
1316                 "<EditRate>25 1</EditRate>"
1317                 "<TimeCodeRate>25</TimeCodeRate>"
1318                 "<StartTime>00:00:00:00</StartTime>"
1319                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1320                 "<SubtitleList>"
1321                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1322                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1323                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1324                 "</Subtitle>"
1325                 "</Font>"
1326                 "</SubtitleList>"
1327                 "</SubtitleReel>";
1328
1329         FILE* xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1330         BOOST_REQUIRE (xml_file);
1331         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1332         fclose (xml_file);
1333         shared_ptr<dcp::SMPTESubtitleAsset> subs (new dcp::SMPTESubtitleAsset(dir / "subs.xml"));
1334         subs->write (dir / "subs.mxf");
1335
1336         shared_ptr<dcp::ReelSubtitleAsset> reel_subs (new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 100, 0));
1337         dcp->cpls().front()->reels().front()->add (reel_subs);
1338         dcp->write_xml (dcp::SMPTE);
1339
1340         vector<boost::filesystem::path> dirs;
1341         dirs.push_back (dir);
1342         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1343         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1344         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1345         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE);
1346 }
1347
1348
1349 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1350 {
1351         boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1352         shared_ptr<dcp::DCP> dcp = make_simple (path, 2);
1353         shared_ptr<dcp::CPL> cpl = dcp->cpls().front();
1354
1355         {
1356                 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
1357                 subs->set_language (dcp::LanguageTag("de-DE"));
1358                 subs->add (simple_subtitle());
1359                 subs->write (path / "subs1.mxf");
1360                 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
1361                 cpl->reels().front()->add (reel_subs);
1362         }
1363
1364         {
1365                 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
1366                 subs->set_language (dcp::LanguageTag("en-US"));
1367                 subs->add (simple_subtitle());
1368                 subs->write (path / "subs2.mxf");
1369                 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
1370                 cpl->reels().back()->add (reel_subs);
1371         }
1372
1373         dcp->write_xml (dcp::SMPTE);
1374
1375         vector<boost::filesystem::path> dirs;
1376         dirs.push_back (path);
1377         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1378         BOOST_REQUIRE_EQUAL (notes.size(), 3U);
1379         list<dcp::VerificationNote>::const_iterator i = notes.begin();
1380         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
1381         ++i;
1382         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1383         ++i;
1384         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
1385 }
1386
1387
1388 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1389 {
1390         boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1391         prepare_directory (dir);
1392         shared_ptr<dcp::DCP> dcp = make_simple (dir, 1);
1393
1394         string const xml =
1395                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1396                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1397                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1398                 "<ContentTitleText>Content</ContentTitleText>"
1399                 "<AnnotationText>Annotation</AnnotationText>"
1400                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1401                 "<ReelNumber>1</ReelNumber>"
1402                 "<Language>de-DE</Language>"
1403                 "<EditRate>25 1</EditRate>"
1404                 "<TimeCodeRate>25</TimeCodeRate>"
1405                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1406                 "<SubtitleList>"
1407                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1408                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1409                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1410                 "</Subtitle>"
1411                 "</Font>"
1412                 "</SubtitleList>"
1413                 "</SubtitleReel>";
1414
1415         FILE* xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1416         BOOST_REQUIRE (xml_file);
1417         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1418         fclose (xml_file);
1419         shared_ptr<dcp::SMPTESubtitleAsset> subs (new dcp::SMPTESubtitleAsset(dir / "subs.xml"));
1420         subs->write (dir / "subs.mxf");
1421
1422         shared_ptr<dcp::ReelSubtitleAsset> reel_subs (new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 100, 0));
1423         dcp->cpls().front()->reels().front()->add (reel_subs);
1424         dcp->write_xml (dcp::SMPTE);
1425
1426         vector<boost::filesystem::path> dirs;
1427         dirs.push_back (dir);
1428         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1429         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1430         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1431         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_SUBTITLE_START_TIME);
1432 }
1433
1434
1435 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1436 {
1437         boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1438         prepare_directory (dir);
1439         shared_ptr<dcp::DCP> dcp = make_simple (dir, 1);
1440
1441         string const xml =
1442                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1443                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1444                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1445                 "<ContentTitleText>Content</ContentTitleText>"
1446                 "<AnnotationText>Annotation</AnnotationText>"
1447                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1448                 "<ReelNumber>1</ReelNumber>"
1449                 "<Language>de-DE</Language>"
1450                 "<EditRate>25 1</EditRate>"
1451                 "<TimeCodeRate>25</TimeCodeRate>"
1452                 "<StartTime>00:00:02:00</StartTime>"
1453                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1454                 "<SubtitleList>"
1455                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1456                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1457                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1458                 "</Subtitle>"
1459                 "</Font>"
1460                 "</SubtitleList>"
1461                 "</SubtitleReel>";
1462
1463         FILE* xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1464         BOOST_REQUIRE (xml_file);
1465         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1466         fclose (xml_file);
1467         shared_ptr<dcp::SMPTESubtitleAsset> subs (new dcp::SMPTESubtitleAsset(dir / "subs.xml"));
1468         subs->write (dir / "subs.mxf");
1469
1470         shared_ptr<dcp::ReelSubtitleAsset> reel_subs (new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 100, 0));
1471         dcp->cpls().front()->reels().front()->add (reel_subs);
1472         dcp->write_xml (dcp::SMPTE);
1473
1474         vector<boost::filesystem::path> dirs;
1475         dirs.push_back (dir);
1476         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
1477         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1478         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1479         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO);
1480 }