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