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