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