c++11 and other tidying.
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #include "verify.h"
35 #include "util.h"
36 #include "j2k.h"
37 #include "reel.h"
38 #include "reel_mono_picture_asset.h"
39 #include "reel_sound_asset.h"
40 #include "cpl.h"
41 #include "dcp.h"
42 #include "openjpeg_image.h"
43 #include "mono_picture_asset.h"
44 #include "stereo_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "interop_subtitle_asset.h"
47 #include "smpte_subtitle_asset.h"
48 #include "reel_closed_caption_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_subtitle_asset.h"
51 #include "compose.hpp"
52 #include "test.h"
53 #include <boost/test/unit_test.hpp>
54 #include <boost/foreach.hpp>
55 #include <boost/algorithm/string.hpp>
56 #include <cstdio>
57 #include <iostream>
58
59 using std::list;
60 using std::pair;
61 using std::string;
62 using std::vector;
63 using std::make_pair;
64 using std::make_shared;
65 using boost::optional;
66 using std::shared_ptr;
67
68
69 static list<pair<string, optional<boost::filesystem::path> > > stages;
70
71 static void
72 stage (string s, optional<boost::filesystem::path> p)
73 {
74         stages.push_back (make_pair (s, p));
75 }
76
77 static void
78 progress (float)
79 {
80
81 }
82
83 static void
84 prepare_directory (boost::filesystem::path path)
85 {
86         using namespace boost::filesystem;
87         remove_all (path);
88         create_directories (path);
89 }
90
91
92 static vector<boost::filesystem::path>
93 setup (int reference_number, int verify_test_number)
94 {
95         prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
96         for (auto i: boost::filesystem::directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
97                 boost::filesystem::copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename());
98         }
99
100         return { dcp::String::compose("build/test/verify_test%1", verify_test_number) };
101
102 }
103
104
105 static
106 void
107 write_dcp_with_single_asset (boost::filesystem::path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::SMPTE)
108 {
109         auto reel = make_shared<dcp::Reel>();
110         reel->add (reel_asset);
111         auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
112         cpl->add (reel);
113         auto dcp = make_shared<dcp::DCP>(dir);
114         dcp->add (cpl);
115         dcp->write_xml (standard);
116 }
117
118
119 /** Class that can alter a file by searching and replacing strings within it.
120  *  On destruction modifies the file whose name was given to the constructor.
121  */
122 class Editor
123 {
124 public:
125         Editor (boost::filesystem::path path)
126                 : _path(path)
127         {
128                 _content = dcp::file_to_string (_path);
129         }
130
131         ~Editor ()
132         {
133                 auto f = fopen(_path.string().c_str(), "w");
134                 BOOST_REQUIRE (f);
135                 fwrite (_content.c_str(), _content.length(), 1, f);
136                 fclose (f);
137         }
138
139         void replace (string a, string b)
140         {
141                 boost::algorithm::replace_all (_content, a, b);
142         }
143
144 private:
145         boost::filesystem::path _path;
146         std::string _content;
147 };
148
149
150 static
151 void
152 dump_notes (list<dcp::VerificationNote> const & notes)
153 {
154         for (auto i: notes) {
155                 std::cout << dcp::note_to_string(i) << "\n";
156         }
157 }
158
159
160 static
161 void
162 check_verify_result (vector<boost::filesystem::path> dir, vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes)
163 {
164         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
165         BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size());
166         auto i = notes.begin();
167         auto j = types_and_codes.begin();
168         while (i != notes.end()) {
169                 BOOST_CHECK_EQUAL (i->type(), j->first);
170                 BOOST_CHECK_EQUAL (i->code(), j->second);
171                 ++i;
172                 ++j;
173         }
174 }
175
176
177 static
178 void
179 check_verify_result_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
180 {
181         auto directories = setup (1, n);
182
183         {
184                 Editor e (file(n));
185                 e.replace (from, to);
186         }
187
188         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
189
190         BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
191         auto i = notes.begin();
192         auto j = codes.begin();
193         while (i != notes.end()) {
194                 BOOST_CHECK_EQUAL (i->code(), *j);
195                 ++i;
196                 ++j;
197         }
198 }
199
200
201 /* Check DCP as-is (should be OK) */
202 BOOST_AUTO_TEST_CASE (verify_test1)
203 {
204         stages.clear ();
205         auto directories = setup (1, 1);
206         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
207
208         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
209         boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml";
210         boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
211
212         auto st = stages.begin();
213         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
214         BOOST_REQUIRE (st->second);
215         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
216         ++st;
217         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
218         BOOST_REQUIRE (st->second);
219         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
220         ++st;
221         BOOST_CHECK_EQUAL (st->first, "Checking reel");
222         BOOST_REQUIRE (!st->second);
223         ++st;
224         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
225         BOOST_REQUIRE (st->second);
226         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
227         ++st;
228         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
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 sound asset hash");
233         BOOST_REQUIRE (st->second);
234         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
235         ++st;
236         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
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 PKL");
241         BOOST_REQUIRE (st->second);
242         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
243         ++st;
244         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
245         BOOST_REQUIRE (st->second);
246         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
247         ++st;
248         BOOST_REQUIRE (st == stages.end());
249
250         BOOST_CHECK_EQUAL (notes.size(), 0);
251 }
252
253 /* Corrupt the MXFs and check that this is spotted */
254 BOOST_AUTO_TEST_CASE (verify_test2)
255 {
256         auto directories = setup (1, 2);
257
258         auto mod = fopen("build/test/verify_test2/video.mxf", "r+b");
259         BOOST_REQUIRE (mod);
260         fseek (mod, 4096, SEEK_SET);
261         int x = 42;
262         fwrite (&x, sizeof(x), 1, mod);
263         fclose (mod);
264
265         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
266         BOOST_REQUIRE (mod);
267         BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
268         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
269         fclose (mod);
270
271         dcp::ASDCPErrorSuspender sus;
272         check_verify_result (
273                 directories,
274                 {
275                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_HASH_INCORRECT },
276                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::SOUND_HASH_INCORRECT }
277                 });
278 }
279
280 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
281 BOOST_AUTO_TEST_CASE (verify_test3)
282 {
283         auto directories = setup (1, 3);
284
285         {
286                 Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml");
287                 e.replace ("<Hash>", "<Hash>x");
288         }
289
290         check_verify_result (
291                 directories,
292                 {
293                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT },
294                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER },
295                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER },
296                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
297                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
298                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
299                 });
300 }
301
302 /* Corrupt the ContentKind in the CPL */
303 BOOST_AUTO_TEST_CASE (verify_test4)
304 {
305         auto directories = setup (1, 4);
306
307         {
308                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
309                 e.replace ("<ContentKind>", "<ContentKind>x");
310         }
311
312         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
313
314         BOOST_REQUIRE_EQUAL (notes.size(), 1);
315         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
316         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
317 }
318
319 static
320 boost::filesystem::path
321 cpl (int n)
322 {
323         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
324 }
325
326 static
327 boost::filesystem::path
328 pkl (int n)
329 {
330         return dcp::String::compose("build/test/verify_test%1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n);
331 }
332
333 static
334 boost::filesystem::path
335 asset_map (int n)
336 {
337         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
338 }
339
340
341 /* FrameRate */
342 BOOST_AUTO_TEST_CASE (verify_test5)
343 {
344         check_verify_result_after_replace (
345                         5, &cpl,
346                         "<FrameRate>24 1", "<FrameRate>99 1",
347                         { dcp::VerificationNote::CPL_HASH_INCORRECT,
348                           dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
349                         );
350 }
351
352 /* Missing asset */
353 BOOST_AUTO_TEST_CASE (verify_test6)
354 {
355         auto directories = setup (1, 6);
356
357         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
358         check_verify_result (directories, {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET }});
359 }
360
361 static
362 boost::filesystem::path
363 assetmap (int n)
364 {
365         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
366 }
367
368 /* Empty asset filename in ASSETMAP */
369 BOOST_AUTO_TEST_CASE (verify_test7)
370 {
371         check_verify_result_after_replace (
372                         7, &assetmap,
373                         "<Path>video.mxf</Path>", "<Path></Path>",
374                         { dcp::VerificationNote::EMPTY_ASSET_PATH }
375                         );
376 }
377
378 /* Mismatched standard */
379 BOOST_AUTO_TEST_CASE (verify_test8)
380 {
381         check_verify_result_after_replace (
382                         8, &cpl,
383                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
384                         { dcp::VerificationNote::MISMATCHED_STANDARD,
385                           dcp::VerificationNote::XML_VALIDATION_ERROR,
386                           dcp::VerificationNote::CPL_HASH_INCORRECT }
387                         );
388 }
389
390 /* Badly formatted <Id> in CPL */
391 BOOST_AUTO_TEST_CASE (verify_test9)
392 {
393         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
394         check_verify_result_after_replace (
395                         9, &cpl,
396                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
397                         { dcp::VerificationNote::XML_VALIDATION_ERROR }
398                         );
399 }
400
401 /* Badly formatted <IssueDate> in CPL */
402 BOOST_AUTO_TEST_CASE (verify_test10)
403 {
404         check_verify_result_after_replace (
405                         10, &cpl,
406                         "<IssueDate>", "<IssueDate>x",
407                         { dcp::VerificationNote::XML_VALIDATION_ERROR,
408                           dcp::VerificationNote::CPL_HASH_INCORRECT }
409                         );
410 }
411
412 /* Badly-formatted <Id> in PKL */
413 BOOST_AUTO_TEST_CASE (verify_test11)
414 {
415         check_verify_result_after_replace (
416                 11, &pkl,
417                 "<Id>urn:uuid:cd4", "<Id>urn:uuid:xd4",
418                 { dcp::VerificationNote::XML_VALIDATION_ERROR }
419                 );
420 }
421
422 /* Badly-formatted <Id> in ASSETMAP */
423 BOOST_AUTO_TEST_CASE (verify_test12)
424 {
425         check_verify_result_after_replace (
426                 12, &asset_map,
427                 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
428                 { dcp::VerificationNote::XML_VALIDATION_ERROR }
429                 );
430 }
431
432 /* Basic test of an Interop DCP */
433 BOOST_AUTO_TEST_CASE (verify_test13)
434 {
435         stages.clear ();
436         auto directories = setup (3, 13);
437         auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
438
439         boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
440         boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
441         boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
442
443         auto st = stages.begin();
444         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
445         BOOST_REQUIRE (st->second);
446         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
447         ++st;
448         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
449         BOOST_REQUIRE (st->second);
450         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
451         ++st;
452         BOOST_CHECK_EQUAL (st->first, "Checking reel");
453         BOOST_REQUIRE (!st->second);
454         ++st;
455         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
456         BOOST_REQUIRE (st->second);
457         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
458         ++st;
459         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
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 sound asset hash");
464         BOOST_REQUIRE (st->second);
465         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
466         ++st;
467         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
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 PKL");
472         BOOST_REQUIRE (st->second);
473         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
474         ++st;
475         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
476         BOOST_REQUIRE (st->second);
477         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
478         ++st;
479         BOOST_REQUIRE (st == stages.end());
480
481         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
482         auto i = notes.begin ();
483         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
484         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
485 }
486
487 /* DCP with a short asset */
488 BOOST_AUTO_TEST_CASE (verify_test14)
489 {
490         auto directories = setup (8, 14);
491         check_verify_result (
492                 directories,
493                 {
494                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
495                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
496                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL },
497                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
498                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL }
499                 });
500 }
501
502
503 static
504 void
505 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
506 {
507         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
508         boost::filesystem::create_directories (dir);
509         auto writer = asset->start_write (dir / "pic.mxf", true);
510         for (int i = 0; i < 24; ++i) {
511                 writer->write (frame.data(), frame.size());
512         }
513         writer->finalize ();
514
515         auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
516         write_dcp_with_single_asset (dir, reel_asset);
517 }
518
519
520 /* DCP with an over-sized JPEG2000 frame */
521 BOOST_AUTO_TEST_CASE (verify_test15)
522 {
523         int const too_big = 1302083 * 2;
524
525         /* Compress a black image */
526         auto image = black_image ();
527         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
528         BOOST_REQUIRE (frame.size() < too_big);
529
530         /* Place it in a bigger block with some zero padding at the end */
531         dcp::ArrayData oversized_frame(too_big);
532         memcpy (oversized_frame.data(), frame.data(), frame.size());
533         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
534
535         boost::filesystem::path const dir("build/test/verify_test15");
536         boost::filesystem::remove_all (dir);
537         dcp_from_frame (oversized_frame, dir);
538
539         check_verify_result (
540                 { dir },
541                 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }}
542                 );
543 }
544
545
546 /* DCP with a nearly over-sized JPEG2000 frame */
547 BOOST_AUTO_TEST_CASE (verify_test16)
548 {
549         int const nearly_too_big = 1302083 * 0.98;
550
551         /* Compress a black image */
552         auto image = black_image ();
553         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
554         BOOST_REQUIRE (frame.size() < nearly_too_big);
555
556         /* Place it in a bigger block with some zero padding at the end */
557         dcp::ArrayData oversized_frame(nearly_too_big);
558         memcpy (oversized_frame.data(), frame.data(), frame.size());
559         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
560
561         boost::filesystem::path const dir("build/test/verify_test16");
562         boost::filesystem::remove_all (dir);
563         dcp_from_frame (oversized_frame, dir);
564
565         check_verify_result (
566                 { dir },
567                 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES }}
568                 );
569 }
570
571
572 /* DCP with a within-range JPEG2000 frame */
573 BOOST_AUTO_TEST_CASE (verify_test17)
574 {
575         /* Compress a black image */
576         auto image = black_image ();
577         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
578         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
579
580         boost::filesystem::path const dir("build/test/verify_test17");
581         boost::filesystem::remove_all (dir);
582         dcp_from_frame (frame, dir);
583
584         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
585         BOOST_REQUIRE_EQUAL (notes.size(), 0);
586 }
587
588
589 /* DCP with valid Interop subtitles */
590 BOOST_AUTO_TEST_CASE (verify_test18)
591 {
592         boost::filesystem::path const dir("build/test/verify_test18");
593         prepare_directory (dir);
594         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
595         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
596         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
597         write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
598
599         check_verify_result (
600                 { dir },
601                 {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }}
602                 );
603 }
604
605
606 /* DCP with broken Interop subtitles */
607 BOOST_AUTO_TEST_CASE (verify_test19)
608 {
609         boost::filesystem::path const dir("build/test/verify_test19");
610         prepare_directory (dir);
611         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
612         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
613         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
614         write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
615
616         {
617                 Editor e (dir / "subs.xml");
618                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
619         }
620
621         check_verify_result (
622                 { dir },
623                 {
624                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
625                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
626                         { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
627                 });
628 }
629
630
631 /* DCP with valid SMPTE subtitles */
632 BOOST_AUTO_TEST_CASE (verify_test20)
633 {
634         boost::filesystem::path const dir("build/test/verify_test20");
635         prepare_directory (dir);
636         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
637         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
638         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
639         write_dcp_with_single_asset (dir, reel_asset);
640
641         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
642         dump_notes (notes);
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::FEATURE);
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::FEATURE);
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::FEATURE);
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::FEATURE);
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         dump_notes (notes);
881         BOOST_REQUIRE_EQUAL (notes.size(), 4U);
882         auto i = notes.begin ();
883         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
884         BOOST_REQUIRE (i->note());
885         BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
886         ++i;
887         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
888         BOOST_REQUIRE (i->note());
889         BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
890         ++i;
891         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
892         BOOST_REQUIRE (i->note());
893         BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
894         ++i;
895         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
896         BOOST_REQUIRE (i->note());
897         BOOST_CHECK_EQUAL (*i->note(), "frobozz");
898         ++i;
899 }
900
901
902 static
903 list<dcp::VerificationNote>
904 check_picture_size (int width, int height, int frame_rate, bool three_d)
905 {
906         using namespace boost::filesystem;
907
908         path dcp_path = "build/test/verify_picture_test";
909         remove_all (dcp_path);
910         create_directories (dcp_path);
911
912         shared_ptr<dcp::PictureAsset> mp;
913         if (three_d) {
914                 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
915         } else {
916                 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
917         }
918         auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
919
920         auto image = black_image (dcp::Size(width, height));
921         auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
922         int const length = three_d ? frame_rate * 2 : frame_rate;
923         for (int i = 0; i < length; ++i) {
924                 picture_writer->write (j2c.data(), j2c.size());
925         }
926         picture_writer->finalize ();
927
928         auto d = make_shared<dcp::DCP>(dcp_path);
929         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::FEATURE);
930         cpl->set_annotation_text ("A Test DCP");
931         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
932
933         auto reel = make_shared<dcp::Reel>();
934
935         if (three_d) {
936                 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
937         } else {
938                 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
939         }
940
941         cpl->add (reel);
942
943         d->add (cpl);
944         d->write_xml (dcp::SMPTE);
945
946         return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
947 }
948
949
950 static
951 void
952 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
953 {
954         auto notes = check_picture_size(width, height, frame_rate, three_d);
955         dump_notes (notes);
956         BOOST_CHECK_EQUAL (notes.size(), 0U);
957 }
958
959
960 static
961 void
962 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
963 {
964         auto notes = check_picture_size(width, height, frame_rate, three_d);
965         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
966         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
967         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
968 }
969
970
971 static
972 void
973 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
974 {
975         auto notes = check_picture_size(width, height, frame_rate, three_d);
976         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
977         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
978         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
979 }
980
981
982 static
983 void
984 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
985 {
986         auto notes = check_picture_size(width, height, frame_rate, three_d);
987         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
988         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
989         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
990 }
991
992
993 BOOST_AUTO_TEST_CASE (verify_picture_size)
994 {
995         using namespace boost::filesystem;
996
997         /* 2K scope */
998         check_picture_size_ok (2048, 858, 24, false);
999         check_picture_size_ok (2048, 858, 25, false);
1000         check_picture_size_ok (2048, 858, 48, false);
1001         check_picture_size_ok (2048, 858, 24, true);
1002         check_picture_size_ok (2048, 858, 25, true);
1003         check_picture_size_ok (2048, 858, 48, true);
1004
1005         /* 2K flat */
1006         check_picture_size_ok (1998, 1080, 24, false);
1007         check_picture_size_ok (1998, 1080, 25, false);
1008         check_picture_size_ok (1998, 1080, 48, false);
1009         check_picture_size_ok (1998, 1080, 24, true);
1010         check_picture_size_ok (1998, 1080, 25, true);
1011         check_picture_size_ok (1998, 1080, 48, true);
1012
1013         /* 4K scope */
1014         check_picture_size_ok (4096, 1716, 24, false);
1015
1016         /* 4K flat */
1017         check_picture_size_ok (3996, 2160, 24, false);
1018
1019         /* Bad frame size */
1020         check_picture_size_bad_frame_size (2050, 858, 24, false);
1021         check_picture_size_bad_frame_size (2048, 658, 25, false);
1022         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1023         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1024
1025         /* Bad 2K frame rate */
1026         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1027         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1028         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1029
1030         /* Bad 4K frame rate */
1031         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1032         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1033
1034         /* No 4K 3D */
1035         auto notes = check_picture_size(3996, 2160, 24, true);
1036         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1037         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1038         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1039 }
1040
1041
1042 static
1043 void
1044 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame)
1045 {
1046         asset->add (
1047                 make_shared<dcp::SubtitleString>(
1048                         optional<string>(),
1049                         false,
1050                         false,
1051                         false,
1052                         dcp::Colour(),
1053                         42,
1054                         1,
1055                         dcp::Time(start_frame, 24, 24),
1056                         dcp::Time(end_frame, 24, 24),
1057                         0,
1058                         dcp::HALIGN_CENTER,
1059                         0,
1060                         dcp::VALIGN_CENTER,
1061                         dcp::DIRECTION_LTR,
1062                         "Hello",
1063                         dcp::NONE,
1064                         dcp::Colour(),
1065                         dcp::Time(),
1066                         dcp::Time()
1067                 )
1068         );
1069 }
1070
1071
1072 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1073 {
1074         boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1075         prepare_directory (dir);
1076
1077         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1078         for (int i = 0; i < 2048; ++i) {
1079                 add_test_subtitle (asset, i * 24, i * 24 + 20);
1080         }
1081         asset->set_language (dcp::LanguageTag("de-DE"));
1082         asset->write (dir / "subs.mxf");
1083         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1084         write_dcp_with_single_asset (dir, reel_asset);
1085
1086         check_verify_result (
1087                 { dir },
1088                 {
1089                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1090                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY },
1091                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES }
1092                 });
1093 }
1094
1095
1096 static
1097 shared_ptr<dcp::SMPTESubtitleAsset>
1098 make_large_subtitle_asset (boost::filesystem::path font_file)
1099 {
1100         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1101         dcp::ArrayData big_fake_font(1024 * 1024);
1102         big_fake_font.write (font_file);
1103         for (int i = 0; i < 116; ++i) {
1104                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1105         }
1106         return asset;
1107 }
1108
1109
1110 template <class T>
1111 void
1112 verify_timed_text_asset_too_large (string name)
1113 {
1114         auto const dir = boost::filesystem::path("build/test") / name;
1115         prepare_directory (dir);
1116         auto asset = make_large_subtitle_asset (dir / "font.ttf");
1117         add_test_subtitle (asset, 0, 20);
1118         asset->set_language (dcp::LanguageTag("de-DE"));
1119         asset->write (dir / "subs.mxf");
1120
1121         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1122         write_dcp_with_single_asset (dir, reel_asset);
1123
1124         check_verify_result (
1125                 { dir },
1126                 {
1127                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES },
1128                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES },
1129                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1130                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1131                 });
1132 }
1133
1134
1135 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1136 {
1137         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1138         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1139 }
1140
1141
1142 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1143 {
1144         boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1145         prepare_directory (dir);
1146         auto dcp = make_simple (dir, 1);
1147
1148         string const xml =
1149                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1150                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1151                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1152                 "<ContentTitleText>Content</ContentTitleText>"
1153                 "<AnnotationText>Annotation</AnnotationText>"
1154                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1155                 "<ReelNumber>1</ReelNumber>"
1156                 "<EditRate>25 1</EditRate>"
1157                 "<TimeCodeRate>25</TimeCodeRate>"
1158                 "<StartTime>00:00:00:00</StartTime>"
1159                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1160                 "<SubtitleList>"
1161                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1162                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1163                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1164                 "</Subtitle>"
1165                 "</Font>"
1166                 "</SubtitleList>"
1167                 "</SubtitleReel>";
1168
1169         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1170         BOOST_REQUIRE (xml_file);
1171         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1172         fclose (xml_file);
1173         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1174         subs->write (dir / "subs.mxf");
1175
1176         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1177         dcp->cpls().front()->reels().front()->add(reel_subs);
1178         dcp->write_xml (dcp::SMPTE);
1179
1180         check_verify_result (
1181                 { dir },
1182                 {
1183                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE },
1184                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1185                 });
1186 }
1187
1188
1189 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1190 {
1191         boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1192         auto dcp = make_simple (path, 2);
1193         auto cpl = dcp->cpls().front();
1194
1195         {
1196                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1197                 subs->set_language (dcp::LanguageTag("de-DE"));
1198                 subs->add (simple_subtitle());
1199                 subs->write (path / "subs1.mxf");
1200                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1201                 cpl->reels().front()->add(reel_subs);
1202         }
1203
1204         {
1205                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1206                 subs->set_language (dcp::LanguageTag("en-US"));
1207                 subs->add (simple_subtitle());
1208                 subs->write (path / "subs2.mxf");
1209                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1210                 cpl->reels().back()->add(reel_subs);
1211         }
1212
1213         dcp->write_xml (dcp::SMPTE);
1214
1215         check_verify_result (
1216                 { path },
1217                 {
1218                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1219                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER },
1220                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
1221                 });
1222 }
1223
1224
1225 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1226 {
1227         boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1228         prepare_directory (dir);
1229         auto dcp = make_simple (dir, 1);
1230
1231         string const xml =
1232                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1233                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1234                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1235                 "<ContentTitleText>Content</ContentTitleText>"
1236                 "<AnnotationText>Annotation</AnnotationText>"
1237                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1238                 "<ReelNumber>1</ReelNumber>"
1239                 "<Language>de-DE</Language>"
1240                 "<EditRate>25 1</EditRate>"
1241                 "<TimeCodeRate>25</TimeCodeRate>"
1242                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1243                 "<SubtitleList>"
1244                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1245                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1246                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1247                 "</Subtitle>"
1248                 "</Font>"
1249                 "</SubtitleList>"
1250                 "</SubtitleReel>";
1251
1252         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1253         BOOST_REQUIRE (xml_file);
1254         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1255         fclose (xml_file);
1256         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1257         subs->write (dir / "subs.mxf");
1258
1259         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1260         dcp->cpls().front()->reels().front()->add(reel_subs);
1261         dcp->write_xml (dcp::SMPTE);
1262
1263         check_verify_result (
1264                 { dir },
1265                 {
1266                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1267                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1268                 });
1269 }
1270
1271
1272 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1273 {
1274         boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1275         prepare_directory (dir);
1276         auto dcp = make_simple (dir, 1);
1277
1278         string const xml =
1279                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1280                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1281                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1282                 "<ContentTitleText>Content</ContentTitleText>"
1283                 "<AnnotationText>Annotation</AnnotationText>"
1284                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1285                 "<ReelNumber>1</ReelNumber>"
1286                 "<Language>de-DE</Language>"
1287                 "<EditRate>25 1</EditRate>"
1288                 "<TimeCodeRate>25</TimeCodeRate>"
1289                 "<StartTime>00:00:02:00</StartTime>"
1290                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1291                 "<SubtitleList>"
1292                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1293                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1294                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1295                 "</Subtitle>"
1296                 "</Font>"
1297                 "</SubtitleList>"
1298                 "</SubtitleReel>";
1299
1300         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1301         BOOST_REQUIRE (xml_file);
1302         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1303         fclose (xml_file);
1304         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1305         subs->write (dir / "subs.mxf");
1306
1307         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1308         dcp->cpls().front()->reels().front()->add(reel_subs);
1309         dcp->write_xml (dcp::SMPTE);
1310
1311         check_verify_result (
1312                 { dir },
1313                 {
1314                         { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO },
1315                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1316                 });
1317 }
1318
1319
1320 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1321 {
1322         auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
1323         prepare_directory (dir);
1324         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1325         asset->set_start_time (dcp::Time());
1326         /* Just too early */
1327         add_test_subtitle (asset, 4 * 24 - 1, 5 * 24);
1328         asset->set_language (dcp::LanguageTag("de-DE"));
1329         asset->write (dir / "subs.mxf");
1330
1331         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1332         write_dcp_with_single_asset (dir, reel_asset);
1333
1334         check_verify_result (
1335                 { dir },
1336                 {
1337                         { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1338                 });
1339 }
1340
1341
1342 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1343 {
1344         auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
1345         prepare_directory (dir);
1346         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1347         asset->set_start_time (dcp::Time());
1348         /* Just late enough */
1349         add_test_subtitle (asset, 4 * 24, 5 * 24);
1350         asset->set_language (dcp::LanguageTag("de-DE"));
1351         asset->write (dir / "subs.mxf");
1352
1353         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1354         write_dcp_with_single_asset (dir, reel_asset);
1355
1356         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1357         dump_notes (notes);
1358         BOOST_REQUIRE (notes.empty());
1359 }
1360
1361
1362 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1363 {
1364         auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
1365         prepare_directory (dir);
1366
1367         auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1368         asset1->set_start_time (dcp::Time());
1369         /* Just late enough */
1370         add_test_subtitle (asset1, 4 * 24, 5 * 24);
1371         asset1->set_language (dcp::LanguageTag("de-DE"));
1372         asset1->write (dir / "subs.mxf");
1373         auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1374         auto reel1 = make_shared<dcp::Reel>();
1375         reel1->add (reel_asset1);
1376
1377         auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1378         asset2->set_start_time (dcp::Time());
1379         /* This would be too early on first reel but should be OK on the second */
1380         add_test_subtitle (asset2, 0, 4 * 24);
1381         asset2->set_language (dcp::LanguageTag("de-DE"));
1382         asset2->write (dir / "subs.mxf");
1383         auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1384         auto reel2 = make_shared<dcp::Reel>();
1385         reel2->add (reel_asset2);
1386
1387         auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
1388         cpl->add (reel1);
1389         cpl->add (reel2);
1390         auto dcp = make_shared<dcp::DCP>(dir);
1391         dcp->add (cpl);
1392         dcp->write_xml (dcp::SMPTE);
1393
1394         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1395         dump_notes (notes);
1396         BOOST_REQUIRE (notes.empty());
1397 }