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