4312194126452a2a0a632a3e15d8f55d142b7ee6
[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 "compose.hpp"
35 #include "cpl.h"
36 #include "dcp.h"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
43 #include "reel.h"
44 #include "reel_closed_caption_asset.h"
45 #include "reel_markers_asset.h"
46 #include "reel_mono_picture_asset.h"
47 #include "reel_sound_asset.h"
48 #include "reel_stereo_picture_asset.h"
49 #include "reel_subtitle_asset.h"
50 #include "smpte_subtitle_asset.h"
51 #include "stereo_picture_asset.h"
52 #include "stream_operators.h"
53 #include "test.h"
54 #include "util.h"
55 #include "verify.h"
56 #include "verify_j2k.h"
57 #include <boost/test/unit_test.hpp>
58 #include <boost/algorithm/string.hpp>
59 #include <cstdio>
60 #include <iostream>
61
62 using std::list;
63 using std::pair;
64 using std::string;
65 using std::vector;
66 using std::make_pair;
67 using std::make_shared;
68 using boost::optional;
69 using namespace boost::filesystem;
70 using std::shared_ptr;
71
72
73 static list<pair<string, optional<path>>> stages;
74 static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml";
75 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
76 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
77
78 static void
79 stage (string s, optional<path> p)
80 {
81         stages.push_back (make_pair (s, p));
82 }
83
84 static void
85 progress (float)
86 {
87
88 }
89
90 static void
91 prepare_directory (path path)
92 {
93         using namespace boost::filesystem;
94         remove_all (path);
95         create_directories (path);
96 }
97
98
99 static path
100 setup (int reference_number, string verify_test_suffix)
101 {
102         auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
103         prepare_directory (dir);
104         for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
105                 copy_file (i.path(), dir / i.path().filename());
106         }
107
108         return dir;
109 }
110
111
112 static
113 shared_ptr<dcp::CPL>
114 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
115 {
116         auto reel = make_shared<dcp::Reel>();
117         reel->add (reel_asset);
118         reel->add (simple_markers());
119
120         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
121         cpl->add (reel);
122         auto dcp = make_shared<dcp::DCP>(dir);
123         dcp->add (cpl);
124         dcp->write_xml (
125                 standard,
126                 dcp::String::compose("libdcp %1", dcp::version),
127                 dcp::String::compose("libdcp %1", dcp::version),
128                 dcp::LocalTime().as_string(),
129                 "hello"
130                 );
131
132         return cpl;
133 }
134
135
136 /** Class that can alter a file by searching and replacing strings within it.
137  *  On destruction modifies the file whose name was given to the constructor.
138  */
139 class Editor
140 {
141 public:
142         Editor (path path)
143                 : _path(path)
144         {
145                 _content = dcp::file_to_string (_path);
146         }
147
148         ~Editor ()
149         {
150                 auto f = fopen(_path.string().c_str(), "w");
151                 BOOST_REQUIRE (f);
152                 fwrite (_content.c_str(), _content.length(), 1, f);
153                 fclose (f);
154         }
155
156         void replace (string a, string b)
157         {
158                 auto old_content = _content;
159                 boost::algorithm::replace_all (_content, a, b);
160                 BOOST_REQUIRE (_content != old_content);
161         }
162
163         void delete_lines (string from, string to)
164         {
165                 vector<string> lines;
166                 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
167                 bool deleting = false;
168                 auto old_content = _content;
169                 _content = "";
170                 for (auto i: lines) {
171                         if (i.find(from) != string::npos) {
172                                 deleting = true;
173                         }
174                         if (!deleting) {
175                                 _content += i + "\n";
176                         }
177                         if (deleting && i.find(to) != string::npos) {
178                                 deleting = false;
179                         }
180                 }
181                 BOOST_REQUIRE (_content != old_content);
182         }
183
184 private:
185         path _path;
186         std::string _content;
187 };
188
189
190 static
191 void
192 dump_notes (vector<dcp::VerificationNote> const & notes)
193 {
194         for (auto i: notes) {
195                 std::cout << dcp::note_to_string(i) << "\n";
196         }
197 }
198
199
200 static
201 void
202 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
203 {
204         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
205         BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
206         for (auto i = 0U; i < notes.size(); ++i) {
207                 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
208         }
209 }
210
211
212 static
213 void
214 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
215 {
216         auto dir = setup (1, suffix);
217
218         {
219                 Editor e (file(suffix));
220                 e.replace (from, to);
221         }
222
223         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
224
225         BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
226         auto i = notes.begin();
227         auto j = codes.begin();
228         while (i != notes.end()) {
229                 BOOST_CHECK_EQUAL (i->code(), *j);
230                 ++i;
231                 ++j;
232         }
233 }
234
235
236 BOOST_AUTO_TEST_CASE (verify_no_error)
237 {
238         stages.clear ();
239         auto dir = setup (1, "no_error");
240         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
241
242         path const cpl_file = dir / dcp_test1_cpl;
243         path const pkl_file = dir / dcp_test1_pkl;
244         path const assetmap_file = dir / "ASSETMAP.xml";
245
246         auto st = stages.begin();
247         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
248         BOOST_REQUIRE (st->second);
249         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
250         ++st;
251         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
252         BOOST_REQUIRE (st->second);
253         BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
254         ++st;
255         BOOST_CHECK_EQUAL (st->first, "Checking reel");
256         BOOST_REQUIRE (!st->second);
257         ++st;
258         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
259         BOOST_REQUIRE (st->second);
260         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
261         ++st;
262         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
263         BOOST_REQUIRE (st->second);
264         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
265         ++st;
266         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
267         BOOST_REQUIRE (st->second);
268         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
269         ++st;
270         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
271         BOOST_REQUIRE (st->second);
272         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
273         ++st;
274         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
275         BOOST_REQUIRE (st->second);
276         BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
277         ++st;
278         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
279         BOOST_REQUIRE (st->second);
280         BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
281         ++st;
282         BOOST_REQUIRE (st == stages.end());
283
284         BOOST_CHECK_EQUAL (notes.size(), 0);
285 }
286
287
288 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
289 {
290         using namespace boost::filesystem;
291
292         auto dir = setup (1, "incorrect_picture_sound_hash");
293
294         auto video_path = path(dir / "video.mxf");
295         auto mod = fopen(video_path.string().c_str(), "r+b");
296         BOOST_REQUIRE (mod);
297         fseek (mod, 4096, SEEK_SET);
298         int x = 42;
299         fwrite (&x, sizeof(x), 1, mod);
300         fclose (mod);
301
302         auto audio_path = path(dir / "audio.mxf");
303         mod = fopen(audio_path.string().c_str(), "r+b");
304         BOOST_REQUIRE (mod);
305         BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
306         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
307         fclose (mod);
308
309         dcp::ASDCPErrorSuspender sus;
310         check_verify_result (
311                 { dir },
312                 {
313                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
314                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
315                 });
316 }
317
318
319 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
320 {
321         using namespace boost::filesystem;
322
323         auto dir = setup (1, "mismatched_picture_sound_hashes");
324
325         {
326                 Editor e (dir / dcp_test1_pkl);
327                 e.replace ("<Hash>", "<Hash>x");
328         }
329
330         check_verify_result (
331                 { dir },
332                 {
333                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
334                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
335                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
336                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xX3bMCBdXEOYEpYmsConNWrWUAGs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
337                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
338                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
339                 });
340 }
341
342
343 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
344 {
345         auto dir = setup (1, "failed_read_content_kind");
346
347         {
348                 Editor e (dir / dcp_test1_cpl);
349                 e.replace ("<ContentKind>", "<ContentKind>x");
350         }
351
352         check_verify_result (
353                 { dir },
354                 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
355                 );
356 }
357
358
359 static
360 path
361 cpl (string suffix)
362 {
363         return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
364 }
365
366
367 static
368 path
369 pkl (string suffix)
370 {
371         return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
372 }
373
374
375 static
376 path
377 asset_map (string suffix)
378 {
379         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
380 }
381
382
383 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
384 {
385         check_verify_result_after_replace (
386                         "invalid_picture_frame_rate", &cpl,
387                         "<FrameRate>24 1", "<FrameRate>99 1",
388                         { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
389                           dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
390                         );
391 }
392
393 BOOST_AUTO_TEST_CASE (verify_missing_asset)
394 {
395         auto dir = setup (1, "missing_asset");
396         remove (dir / "video.mxf");
397         check_verify_result (
398                 { dir },
399                 {
400                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
401                 });
402 }
403
404
405 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
406 {
407         check_verify_result_after_replace (
408                         "empty_asset_path", &asset_map,
409                         "<Path>video.mxf</Path>", "<Path></Path>",
410                         { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
411                         );
412 }
413
414
415 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
416 {
417         check_verify_result_after_replace (
418                         "mismatched_standard", &cpl,
419                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
420                         { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
421                           dcp::VerificationNote::Code::INVALID_XML,
422                           dcp::VerificationNote::Code::INVALID_XML,
423                           dcp::VerificationNote::Code::INVALID_XML,
424                           dcp::VerificationNote::Code::INVALID_XML,
425                           dcp::VerificationNote::Code::INVALID_XML,
426                           dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
427                         );
428 }
429
430
431 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
432 {
433         /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
434         check_verify_result_after_replace (
435                         "invalid_xml_cpl_id", &cpl,
436                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
437                         { dcp::VerificationNote::Code::INVALID_XML }
438                         );
439 }
440
441
442 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
443 {
444         check_verify_result_after_replace (
445                         "invalid_xml_issue_date", &cpl,
446                         "<IssueDate>", "<IssueDate>x",
447                         { dcp::VerificationNote::Code::INVALID_XML,
448                           dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
449                         );
450 }
451
452
453 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
454 {
455         check_verify_result_after_replace (
456                 "invalid_xml_pkl_id", &pkl,
457                 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
458                 { dcp::VerificationNote::Code::INVALID_XML }
459                 );
460 }
461
462
463 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
464 {
465         check_verify_result_after_replace (
466                 "invalix_xml_asset_map_id", &asset_map,
467                 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
468                 { dcp::VerificationNote::Code::INVALID_XML }
469                 );
470 }
471
472
473 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
474 {
475         stages.clear ();
476         auto dir = setup (3, "verify_invalid_standard");
477         auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
478
479         path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
480         path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
481         path const assetmap_file = dir / "ASSETMAP";
482
483         auto st = stages.begin();
484         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
485         BOOST_REQUIRE (st->second);
486         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
487         ++st;
488         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
489         BOOST_REQUIRE (st->second);
490         BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
491         ++st;
492         BOOST_CHECK_EQUAL (st->first, "Checking reel");
493         BOOST_REQUIRE (!st->second);
494         ++st;
495         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
496         BOOST_REQUIRE (st->second);
497         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
498         ++st;
499         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
500         BOOST_REQUIRE (st->second);
501         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
502         ++st;
503         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
504         BOOST_REQUIRE (st->second);
505         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
506         ++st;
507         BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
508         BOOST_REQUIRE (st->second);
509         BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
510         ++st;
511         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
512         BOOST_REQUIRE (st->second);
513         BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
514         ++st;
515         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
516         BOOST_REQUIRE (st->second);
517         BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
518         ++st;
519         BOOST_REQUIRE (st == stages.end());
520
521         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
522         auto i = notes.begin ();
523         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
524         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
525 }
526
527 /* DCP with a short asset */
528 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
529 {
530         auto dir = setup (8, "invalid_duration");
531         check_verify_result (
532                 { dir },
533                 {
534                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
535                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
536                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
537                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
538                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
539                 });
540 }
541
542
543 static
544 shared_ptr<dcp::CPL>
545 dcp_from_frame (dcp::ArrayData const& frame, path dir)
546 {
547         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
548         create_directories (dir);
549         auto writer = asset->start_write (dir / "pic.mxf", true);
550         for (int i = 0; i < 24; ++i) {
551                 writer->write (frame.data(), frame.size());
552         }
553         writer->finalize ();
554
555         auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
556         return write_dcp_with_single_asset (dir, reel_asset);
557 }
558
559
560 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
561 {
562         int const too_big = 1302083 * 2;
563
564         /* Compress a black image */
565         auto image = black_image ();
566         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
567         BOOST_REQUIRE (frame.size() < too_big);
568
569         /* Place it in a bigger block with some zero padding at the end */
570         dcp::ArrayData oversized_frame(too_big);
571         memcpy (oversized_frame.data(), frame.data(), frame.size());
572         memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
573
574         path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
575         prepare_directory (dir);
576         auto cpl = dcp_from_frame (oversized_frame, dir);
577
578         check_verify_result (
579                 { dir },
580                 {
581                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
582                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
583                 });
584 }
585
586
587 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
588 {
589         int const nearly_too_big = 1302083 * 0.98;
590
591         /* Compress a black image */
592         auto image = black_image ();
593         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
594         BOOST_REQUIRE (frame.size() < nearly_too_big);
595
596         /* Place it in a bigger block with some zero padding at the end */
597         dcp::ArrayData oversized_frame(nearly_too_big);
598         memcpy (oversized_frame.data(), frame.data(), frame.size());
599         memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
600
601         path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
602         prepare_directory (dir);
603         auto cpl = dcp_from_frame (oversized_frame, dir);
604
605         check_verify_result (
606                 { dir },
607                 {
608                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
609                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
610                 });
611 }
612
613
614 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
615 {
616         /* Compress a black image */
617         auto image = black_image ();
618         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
619         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
620
621         path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
622         prepare_directory (dir);
623         auto cpl = dcp_from_frame (frame, dir);
624
625         check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
626 }
627
628
629 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
630 {
631         path const dir("build/test/verify_valid_interop_subtitles");
632         prepare_directory (dir);
633         copy_file ("test/data/subs1.xml", dir / "subs.xml");
634         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
635         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
636         write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
637
638         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
639 }
640
641
642 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
643 {
644         using namespace boost::filesystem;
645
646         path const dir("build/test/verify_invalid_interop_subtitles");
647         prepare_directory (dir);
648         copy_file ("test/data/subs1.xml", dir / "subs.xml");
649         auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
650         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
651         write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
652
653         {
654                 Editor e (dir / "subs.xml");
655                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
656         }
657
658         check_verify_result (
659                 { dir },
660                 {
661                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
662                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
663                         {
664                                 dcp::VerificationNote::Type::ERROR,
665                                 dcp::VerificationNote::Code::INVALID_XML,
666                                 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
667                                 path(),
668                                 29
669                         }
670                 });
671 }
672
673
674 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
675 {
676         path const dir("build/test/verify_valid_smpte_subtitles");
677         prepare_directory (dir);
678         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
679         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
680         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
681         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
682
683         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
684 }
685
686
687 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
688 {
689         using namespace boost::filesystem;
690
691         path const dir("build/test/verify_invalid_smpte_subtitles");
692         prepare_directory (dir);
693         copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
694         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
695         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
696         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
697
698         check_verify_result (
699                 { dir },
700                 {
701                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
702                         {
703                                 dcp::VerificationNote::Type::ERROR,
704                                 dcp::VerificationNote::Code::INVALID_XML,
705                                 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
706                                 path(),
707                                 2
708                         },
709                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
710                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
711                 });
712 }
713
714
715 BOOST_AUTO_TEST_CASE (verify_external_asset)
716 {
717         path const ov_dir("build/test/verify_external_asset");
718         prepare_directory (ov_dir);
719
720         auto image = black_image ();
721         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
722         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
723         dcp_from_frame (frame, ov_dir);
724
725         dcp::DCP ov (ov_dir);
726         ov.read ();
727
728         path const vf_dir("build/test/verify_external_asset_vf");
729         prepare_directory (vf_dir);
730
731         auto picture = ov.cpls()[0]->reels()[0]->main_picture();
732         auto cpl = write_dcp_with_single_asset (vf_dir, picture);
733
734         check_verify_result (
735                 { vf_dir },
736                 {
737                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
738                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
739                 });
740 }
741
742
743 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
744 {
745         path const dir("build/test/verify_valid_cpl_metadata");
746         prepare_directory (dir);
747
748         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
749         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
750         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
751
752         auto reel = make_shared<dcp::Reel>();
753         reel->add (reel_asset);
754
755         reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
756         reel->add (simple_markers(16 * 24));
757
758         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
759         cpl->add (reel);
760         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
761         cpl->set_main_sound_sample_rate (48000);
762         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
763         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
764         cpl->set_version_number (1);
765
766         dcp::DCP dcp (dir);
767         dcp.add (cpl);
768         dcp.write_xml (
769                 dcp::Standard::SMPTE,
770                 dcp::String::compose("libdcp %1", dcp::version),
771                 dcp::String::compose("libdcp %1", dcp::version),
772                 dcp::LocalTime().as_string(),
773                 "hello"
774                 );
775 }
776
777
778 path find_cpl (path dir)
779 {
780         for (auto i: directory_iterator(dir)) {
781                 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
782                         return i.path();
783                 }
784         }
785
786         BOOST_REQUIRE (false);
787         return {};
788 }
789
790
791 /* DCP with invalid CompositionMetadataAsset */
792 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
793 {
794         using namespace boost::filesystem;
795
796         path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
797         prepare_directory (dir);
798
799         auto reel = make_shared<dcp::Reel>();
800         reel->add (black_picture_asset(dir));
801         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
802         cpl->add (reel);
803         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
804         cpl->set_main_sound_sample_rate (48000);
805         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
806         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
807         cpl->set_version_number (1);
808
809         reel->add (simple_markers());
810
811         dcp::DCP dcp (dir);
812         dcp.add (cpl);
813         dcp.write_xml (
814                 dcp::Standard::SMPTE,
815                 dcp::String::compose("libdcp %1", dcp::version),
816                 dcp::String::compose("libdcp %1", dcp::version),
817                 dcp::LocalTime().as_string(),
818                 "hello"
819                 );
820
821         {
822                 Editor e (find_cpl(dir));
823                 e.replace ("MainSound", "MainSoundX");
824         }
825
826         check_verify_result (
827                 { dir },
828                 {
829                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
830                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
831                         {
832                                 dcp::VerificationNote::Type::ERROR,
833                                 dcp::VerificationNote::Code::INVALID_XML,
834                                 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
835                                        "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
836                                        "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
837                                        "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
838                                        "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
839                                        "ExtensionMetadataList?,)'"),
840                                 canonical(cpl->file().get()),
841                                 75
842                         },
843                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
844                 });
845 }
846
847
848 /* DCP with invalid CompositionMetadataAsset */
849 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
850 {
851         path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
852         prepare_directory (dir);
853
854         auto reel = make_shared<dcp::Reel>();
855         reel->add (black_picture_asset(dir));
856         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
857         cpl->add (reel);
858         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
859         cpl->set_main_sound_sample_rate (48000);
860         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
861         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
862
863         dcp::DCP dcp (dir);
864         dcp.add (cpl);
865         dcp.write_xml (
866                 dcp::Standard::SMPTE,
867                 dcp::String::compose("libdcp %1", dcp::version),
868                 dcp::String::compose("libdcp %1", dcp::version),
869                 dcp::LocalTime().as_string(),
870                 "hello"
871                 );
872
873         {
874                 Editor e (find_cpl(dir));
875                 e.replace ("meta:Width", "meta:WidthX");
876         }
877
878         check_verify_result (
879                 { dir },
880                 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
881                 );
882 }
883
884
885 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
886 {
887         path const dir("build/test/verify_invalid_language1");
888         prepare_directory (dir);
889         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
890         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
891         asset->_language = "wrong-andbad";
892         asset->write (dir / "subs.mxf");
893         auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
894         reel_asset->_language = "badlang";
895         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
896
897         check_verify_result (
898                 { dir },
899                 {
900                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
901                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
902                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
903                 });
904 }
905
906
907 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
908 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
909 {
910         path const dir("build/test/verify_invalid_language2");
911         prepare_directory (dir);
912         copy_file ("test/data/subs.mxf", dir / "subs.mxf");
913         auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
914         asset->_language = "wrong-andbad";
915         asset->write (dir / "subs.mxf");
916         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
917         reel_asset->_language = "badlang";
918         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
919
920         check_verify_result (
921                 {dir},
922                 {
923                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
924                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
925                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
926                 });
927 }
928
929
930 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
931  * the release territory.
932  */
933 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
934 {
935         path const dir("build/test/verify_invalid_language3");
936         prepare_directory (dir);
937
938         auto picture = simple_picture (dir, "foo");
939         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
940         auto reel = make_shared<dcp::Reel>();
941         reel->add (reel_picture);
942         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
943         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
944         reel->add (reel_sound);
945         reel->add (simple_markers());
946
947         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
948         cpl->add (reel);
949         cpl->_additional_subtitle_languages.push_back("this-is-wrong");
950         cpl->_additional_subtitle_languages.push_back("andso-is-this");
951         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
952         cpl->set_main_sound_sample_rate (48000);
953         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
954         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
955         cpl->set_version_number (1);
956         cpl->_release_territory = "fred-jim";
957         auto dcp = make_shared<dcp::DCP>(dir);
958         dcp->add (cpl);
959         dcp->write_xml (
960                 dcp::Standard::SMPTE,
961                 dcp::String::compose("libdcp %1", dcp::version),
962                 dcp::String::compose("libdcp %1", dcp::version),
963                 dcp::LocalTime().as_string(),
964                 "hello"
965                 );
966
967         check_verify_result (
968                 { dir },
969                 {
970                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
971                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
972                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
973                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
974                 });
975 }
976
977
978 static
979 vector<dcp::VerificationNote>
980 check_picture_size (int width, int height, int frame_rate, bool three_d)
981 {
982         using namespace boost::filesystem;
983
984         path dcp_path = "build/test/verify_picture_test";
985         prepare_directory (dcp_path);
986
987         shared_ptr<dcp::PictureAsset> mp;
988         if (three_d) {
989                 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
990         } else {
991                 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
992         }
993         auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
994
995         auto image = black_image (dcp::Size(width, height));
996         auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
997         int const length = three_d ? frame_rate * 2 : frame_rate;
998         for (int i = 0; i < length; ++i) {
999                 picture_writer->write (j2c.data(), j2c.size());
1000         }
1001         picture_writer->finalize ();
1002
1003         auto d = make_shared<dcp::DCP>(dcp_path);
1004         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1005         cpl->set_annotation_text ("A Test DCP");
1006         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1007         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1008         cpl->set_main_sound_sample_rate (48000);
1009         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1010         cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1011         cpl->set_version_number (1);
1012
1013         auto reel = make_shared<dcp::Reel>();
1014
1015         if (three_d) {
1016                 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1017         } else {
1018                 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1019         }
1020
1021         reel->add (simple_markers(frame_rate));
1022
1023         cpl->add (reel);
1024
1025         d->add (cpl);
1026         d->write_xml (
1027                 dcp::Standard::SMPTE,
1028                 dcp::String::compose("libdcp %1", dcp::version),
1029                 dcp::String::compose("libdcp %1", dcp::version),
1030                 dcp::LocalTime().as_string(),
1031                 "A Test DCP"
1032                 );
1033
1034         return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1035 }
1036
1037
1038 static
1039 void
1040 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1041 {
1042         auto notes = check_picture_size(width, height, frame_rate, three_d);
1043         dump_notes (notes);
1044         BOOST_CHECK_EQUAL (notes.size(), 0U);
1045 }
1046
1047
1048 static
1049 void
1050 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1051 {
1052         auto notes = check_picture_size(width, height, frame_rate, three_d);
1053         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1054         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1055         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1056 }
1057
1058
1059 static
1060 void
1061 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1062 {
1063         auto notes = check_picture_size(width, height, frame_rate, three_d);
1064         BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1065         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1066         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1067 }
1068
1069
1070 static
1071 void
1072 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1073 {
1074         auto notes = check_picture_size(width, height, frame_rate, three_d);
1075         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1076         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1077         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1078 }
1079
1080
1081 BOOST_AUTO_TEST_CASE (verify_picture_size)
1082 {
1083         using namespace boost::filesystem;
1084
1085         /* 2K scope */
1086         check_picture_size_ok (2048, 858, 24, false);
1087         check_picture_size_ok (2048, 858, 25, false);
1088         check_picture_size_ok (2048, 858, 48, false);
1089         check_picture_size_ok (2048, 858, 24, true);
1090         check_picture_size_ok (2048, 858, 25, true);
1091         check_picture_size_ok (2048, 858, 48, true);
1092
1093         /* 2K flat */
1094         check_picture_size_ok (1998, 1080, 24, false);
1095         check_picture_size_ok (1998, 1080, 25, false);
1096         check_picture_size_ok (1998, 1080, 48, false);
1097         check_picture_size_ok (1998, 1080, 24, true);
1098         check_picture_size_ok (1998, 1080, 25, true);
1099         check_picture_size_ok (1998, 1080, 48, true);
1100
1101         /* 4K scope */
1102         check_picture_size_ok (4096, 1716, 24, false);
1103
1104         /* 4K flat */
1105         check_picture_size_ok (3996, 2160, 24, false);
1106
1107         /* Bad frame size */
1108         check_picture_size_bad_frame_size (2050, 858, 24, false);
1109         check_picture_size_bad_frame_size (2048, 658, 25, false);
1110         check_picture_size_bad_frame_size (1920, 1080, 48, true);
1111         check_picture_size_bad_frame_size (4000, 3000, 24, true);
1112
1113         /* Bad 2K frame rate */
1114         check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1115         check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1116         check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1117
1118         /* Bad 4K frame rate */
1119         check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1120         check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1121
1122         /* No 4K 3D */
1123         auto notes = check_picture_size(3996, 2160, 24, true);
1124         BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1125         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1126         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1127 }
1128
1129
1130 static
1131 void
1132 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1133 {
1134         asset->add (
1135                 make_shared<dcp::SubtitleString>(
1136                         optional<string>(),
1137                         false,
1138                         false,
1139                         false,
1140                         dcp::Colour(),
1141                         42,
1142                         1,
1143                         dcp::Time(start_frame, 24, 24),
1144                         dcp::Time(end_frame, 24, 24),
1145                         0,
1146                         dcp::HAlign::CENTER,
1147                         v_position,
1148                         dcp::VAlign::CENTER,
1149                         dcp::Direction::LTR,
1150                         text,
1151                         dcp::Effect::NONE,
1152                         dcp::Colour(),
1153                         dcp::Time(),
1154                         dcp::Time()
1155                 )
1156         );
1157 }
1158
1159
1160 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1161 {
1162         path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1163         prepare_directory (dir);
1164
1165         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1166         for (int i = 0; i < 2048; ++i) {
1167                 add_test_subtitle (asset, i * 24, i * 24 + 20);
1168         }
1169         asset->set_language (dcp::LanguageTag("de-DE"));
1170         asset->write (dir / "subs.mxf");
1171         auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1172         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1173
1174         check_verify_result (
1175                 { dir },
1176                 {
1177                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1178                         {
1179                                 dcp::VerificationNote::Type::BV21_ERROR,
1180                                 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1181                                 string("413262"),
1182                                 canonical(dir / "subs.mxf")
1183                         },
1184                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1185                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1186                 });
1187 }
1188
1189
1190 static
1191 shared_ptr<dcp::SMPTESubtitleAsset>
1192 make_large_subtitle_asset (path font_file)
1193 {
1194         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1195         dcp::ArrayData big_fake_font(1024 * 1024);
1196         big_fake_font.write (font_file);
1197         for (int i = 0; i < 116; ++i) {
1198                 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1199         }
1200         return asset;
1201 }
1202
1203
1204 template <class T>
1205 void
1206 verify_timed_text_asset_too_large (string name)
1207 {
1208         auto const dir = path("build/test") / name;
1209         prepare_directory (dir);
1210         auto asset = make_large_subtitle_asset (dir / "font.ttf");
1211         add_test_subtitle (asset, 0, 20);
1212         asset->set_language (dcp::LanguageTag("de-DE"));
1213         asset->write (dir / "subs.mxf");
1214
1215         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1216         auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1217
1218         check_verify_result (
1219                 { dir },
1220                 {
1221                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1222                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1223                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1224                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1225                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1226                 });
1227 }
1228
1229
1230 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1231 {
1232         verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1233         verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1234 }
1235
1236
1237 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1238 {
1239         path dir = "build/test/verify_missing_subtitle_language";
1240         prepare_directory (dir);
1241         auto dcp = make_simple (dir, 1, 240);
1242
1243         string const xml =
1244                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1245                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1246                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1247                 "<ContentTitleText>Content</ContentTitleText>"
1248                 "<AnnotationText>Annotation</AnnotationText>"
1249                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1250                 "<ReelNumber>1</ReelNumber>"
1251                 "<EditRate>25 1</EditRate>"
1252                 "<TimeCodeRate>25</TimeCodeRate>"
1253                 "<StartTime>00:00:00:00</StartTime>"
1254                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1255                 "<SubtitleList>"
1256                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1257                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1258                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1259                 "</Subtitle>"
1260                 "</Font>"
1261                 "</SubtitleList>"
1262                 "</SubtitleReel>";
1263
1264         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1265         BOOST_REQUIRE (xml_file);
1266         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1267         fclose (xml_file);
1268         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1269         subs->write (dir / "subs.mxf");
1270
1271         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1272         dcp->cpls().front()->reels().front()->add(reel_subs);
1273         dcp->write_xml (
1274                 dcp::Standard::SMPTE,
1275                 dcp::String::compose("libdcp %1", dcp::version),
1276                 dcp::String::compose("libdcp %1", dcp::version),
1277                 dcp::LocalTime().as_string(),
1278                 "A Test DCP"
1279                 );
1280
1281         check_verify_result (
1282                 { dir },
1283                 {
1284                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1285                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1286                 });
1287 }
1288
1289
1290 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1291 {
1292         path path ("build/test/verify_mismatched_subtitle_languages");
1293         auto dcp = make_simple (path, 2, 240);
1294         auto cpl = dcp->cpls()[0];
1295
1296         {
1297                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1298                 subs->set_language (dcp::LanguageTag("de-DE"));
1299                 subs->add (simple_subtitle());
1300                 subs->write (path / "subs1.mxf");
1301                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1302                 cpl->reels()[0]->add(reel_subs);
1303         }
1304
1305         {
1306                 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1307                 subs->set_language (dcp::LanguageTag("en-US"));
1308                 subs->add (simple_subtitle());
1309                 subs->write (path / "subs2.mxf");
1310                 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1311                 cpl->reels()[1]->add(reel_subs);
1312         }
1313
1314         dcp->write_xml (
1315                 dcp::Standard::SMPTE,
1316                 dcp::String::compose("libdcp %1", dcp::version),
1317                 dcp::String::compose("libdcp %1", dcp::version),
1318                 dcp::LocalTime().as_string(),
1319                 "A Test DCP"
1320                 );
1321
1322         check_verify_result (
1323                 { path },
1324                 {
1325                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1326                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1327                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1328                 });
1329 }
1330
1331
1332 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1333 {
1334         path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1335         auto dcp = make_simple (path, 2, 240);
1336         auto cpl = dcp->cpls()[0];
1337
1338         {
1339                 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1340                 ccaps->set_language (dcp::LanguageTag("de-DE"));
1341                 ccaps->add (simple_subtitle());
1342                 ccaps->write (path / "subs1.mxf");
1343                 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1344                 cpl->reels()[0]->add(reel_ccaps);
1345         }
1346
1347         {
1348                 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1349                 ccaps->set_language (dcp::LanguageTag("en-US"));
1350                 ccaps->add (simple_subtitle());
1351                 ccaps->write (path / "subs2.mxf");
1352                 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1353                 cpl->reels()[1]->add(reel_ccaps);
1354         }
1355
1356         dcp->write_xml (
1357                 dcp::Standard::SMPTE,
1358                 dcp::String::compose("libdcp %1", dcp::version),
1359                 dcp::String::compose("libdcp %1", dcp::version),
1360                 dcp::LocalTime().as_string(),
1361                 "A Test DCP"
1362                 );
1363
1364         check_verify_result (
1365                 { path },
1366                 {
1367                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1368                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1369                 });
1370 }
1371
1372
1373 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1374 {
1375         path dir = "build/test/verify_missing_subtitle_start_time";
1376         prepare_directory (dir);
1377         auto dcp = make_simple (dir, 1, 240);
1378
1379         string const xml =
1380                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1381                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1382                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1383                 "<ContentTitleText>Content</ContentTitleText>"
1384                 "<AnnotationText>Annotation</AnnotationText>"
1385                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1386                 "<ReelNumber>1</ReelNumber>"
1387                 "<Language>de-DE</Language>"
1388                 "<EditRate>25 1</EditRate>"
1389                 "<TimeCodeRate>25</TimeCodeRate>"
1390                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1391                 "<SubtitleList>"
1392                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1393                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1394                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1395                 "</Subtitle>"
1396                 "</Font>"
1397                 "</SubtitleList>"
1398                 "</SubtitleReel>";
1399
1400         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1401         BOOST_REQUIRE (xml_file);
1402         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1403         fclose (xml_file);
1404         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1405         subs->write (dir / "subs.mxf");
1406
1407         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1408         dcp->cpls().front()->reels().front()->add(reel_subs);
1409         dcp->write_xml (
1410                 dcp::Standard::SMPTE,
1411                 dcp::String::compose("libdcp %1", dcp::version),
1412                 dcp::String::compose("libdcp %1", dcp::version),
1413                 dcp::LocalTime().as_string(),
1414                 "A Test DCP"
1415                 );
1416
1417         check_verify_result (
1418                 { dir },
1419                 {
1420                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1421                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1422                 });
1423 }
1424
1425
1426 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1427 {
1428         path dir = "build/test/verify_invalid_subtitle_start_time";
1429         prepare_directory (dir);
1430         auto dcp = make_simple (dir, 1, 240);
1431
1432         string const xml =
1433                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1434                 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1435                 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1436                 "<ContentTitleText>Content</ContentTitleText>"
1437                 "<AnnotationText>Annotation</AnnotationText>"
1438                 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1439                 "<ReelNumber>1</ReelNumber>"
1440                 "<Language>de-DE</Language>"
1441                 "<EditRate>25 1</EditRate>"
1442                 "<TimeCodeRate>25</TimeCodeRate>"
1443                 "<StartTime>00:00:02:00</StartTime>"
1444                 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1445                 "<SubtitleList>"
1446                 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1447                 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1448                 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1449                 "</Subtitle>"
1450                 "</Font>"
1451                 "</SubtitleList>"
1452                 "</SubtitleReel>";
1453
1454         auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1455         BOOST_REQUIRE (xml_file);
1456         fwrite (xml.c_str(), xml.size(), 1, xml_file);
1457         fclose (xml_file);
1458         auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1459         subs->write (dir / "subs.mxf");
1460
1461         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1462         dcp->cpls().front()->reels().front()->add(reel_subs);
1463         dcp->write_xml (
1464                 dcp::Standard::SMPTE,
1465                 dcp::String::compose("libdcp %1", dcp::version),
1466                 dcp::String::compose("libdcp %1", dcp::version),
1467                 dcp::LocalTime().as_string(),
1468                 "A Test DCP"
1469                 );
1470
1471         check_verify_result (
1472                 { dir },
1473                 {
1474                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1475                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1476                 });
1477 }
1478
1479
1480 class TestText
1481 {
1482 public:
1483         TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1484                 : in(in_)
1485                 , out(out_)
1486                 , v_position(v_position_)
1487                 , text(text_)
1488         {}
1489
1490         int in;
1491         int out;
1492         float v_position;
1493         string text;
1494 };
1495
1496
1497 template <class T>
1498 shared_ptr<dcp::CPL>
1499 dcp_with_text (path dir, vector<TestText> subs)
1500 {
1501         prepare_directory (dir);
1502         auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1503         asset->set_start_time (dcp::Time());
1504         for (auto i: subs) {
1505                 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1506         }
1507         asset->set_language (dcp::LanguageTag("de-DE"));
1508         asset->write (dir / "subs.mxf");
1509
1510         auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1511         return write_dcp_with_single_asset (dir, reel_asset);
1512 }
1513
1514
1515 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1516 {
1517         auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1518         /* Just too early */
1519         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1520         check_verify_result (
1521                 { dir },
1522                 {
1523                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1524                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1525                 });
1526
1527 }
1528
1529
1530 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1531 {
1532         auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1533         /* Just late enough */
1534         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1535         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1536 }
1537
1538
1539 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1540 {
1541         auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1542         prepare_directory (dir);
1543
1544         auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1545         asset1->set_start_time (dcp::Time());
1546         /* Just late enough */
1547         add_test_subtitle (asset1, 4 * 24, 5 * 24);
1548         asset1->set_language (dcp::LanguageTag("de-DE"));
1549         asset1->write (dir / "subs1.mxf");
1550         auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1551         auto reel1 = make_shared<dcp::Reel>();
1552         reel1->add (reel_asset1);
1553         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1554         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1555         reel1->add (markers1);
1556
1557         auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1558         asset2->set_start_time (dcp::Time());
1559         /* This would be too early on first reel but should be OK on the second */
1560         add_test_subtitle (asset2, 0, 4 * 24);
1561         asset2->set_language (dcp::LanguageTag("de-DE"));
1562         asset2->write (dir / "subs2.mxf");
1563         auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1564         auto reel2 = make_shared<dcp::Reel>();
1565         reel2->add (reel_asset2);
1566         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1567         markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1568         reel2->add (markers2);
1569
1570         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1571         cpl->add (reel1);
1572         cpl->add (reel2);
1573         auto dcp = make_shared<dcp::DCP>(dir);
1574         dcp->add (cpl);
1575         dcp->write_xml (
1576                 dcp::Standard::SMPTE,
1577                 dcp::String::compose("libdcp %1", dcp::version),
1578                 dcp::String::compose("libdcp %1", dcp::version),
1579                 dcp::LocalTime().as_string(),
1580                 "hello"
1581                 );
1582
1583
1584         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1585 }
1586
1587
1588 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1589 {
1590         auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1591         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1592                 dir,
1593                 {
1594                         { 4 * 24,     5 * 24 },
1595                         { 5 * 24 + 1, 6 * 24 },
1596                 });
1597         check_verify_result (
1598                 {dir},
1599                 {
1600                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1601                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1602                 });
1603 }
1604
1605
1606 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1607 {
1608         auto const dir = path("build/test/verify_valid_subtitle_spacing");
1609         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1610                 dir,
1611                 {
1612                         { 4 * 24,      5 * 24 },
1613                         { 5 * 24 + 16, 8 * 24 },
1614                 });
1615         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1616 }
1617
1618
1619 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1620 {
1621         auto const dir = path("build/test/verify_invalid_subtitle_duration");
1622         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1623         check_verify_result (
1624                 {dir},
1625                 {
1626                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1627                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1628                 });
1629 }
1630
1631
1632 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1633 {
1634         auto const dir = path("build/test/verify_valid_subtitle_duration");
1635         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1636         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1637 }
1638
1639
1640 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1641 {
1642         auto const dir = path ("build/test/invalid_subtitle_line_count1");
1643         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1644                 dir,
1645                 {
1646                         { 96, 200, 0.0, "We" },
1647                         { 96, 200, 0.1, "have" },
1648                         { 96, 200, 0.2, "four" },
1649                         { 96, 200, 0.3, "lines" }
1650                 });
1651         check_verify_result (
1652                 {dir},
1653                 {
1654                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1655                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1656                 });
1657 }
1658
1659
1660 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1661 {
1662         auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1663         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1664                 dir,
1665                 {
1666                         { 96, 200, 0.0, "We" },
1667                         { 96, 200, 0.1, "have" },
1668                         { 96, 200, 0.2, "four" },
1669                 });
1670         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1671 }
1672
1673
1674 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1675 {
1676         auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1677         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1678                 dir,
1679                 {
1680                         { 96, 300, 0.0, "We" },
1681                         { 96, 300, 0.1, "have" },
1682                         { 150, 180, 0.2, "four" },
1683                         { 150, 180, 0.3, "lines" }
1684                 });
1685         check_verify_result (
1686                 {dir},
1687                 {
1688                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1689                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1690                 });
1691 }
1692
1693
1694 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1695 {
1696         auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1697         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1698                 dir,
1699                 {
1700                         { 96, 300, 0.0, "We" },
1701                         { 96, 300, 0.1, "have" },
1702                         { 150, 180, 0.2, "four" },
1703                         { 190, 250, 0.3, "lines" }
1704                 });
1705         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1706 }
1707
1708
1709 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1710 {
1711         auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1712         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1713                 dir,
1714                 {
1715                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1716                 });
1717         check_verify_result (
1718                 {dir},
1719                 {
1720                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1721                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1722                 });
1723 }
1724
1725
1726 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1727 {
1728         auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1729         auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1730                 dir,
1731                 {
1732                         { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1733                 });
1734         check_verify_result (
1735                 {dir},
1736                 {
1737                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1738                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1739                 });
1740 }
1741
1742
1743 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1744 {
1745         auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1746         auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1747                 dir,
1748                 {
1749                         { 96, 200, 0.0, "We" },
1750                         { 96, 200, 0.1, "have" },
1751                         { 96, 200, 0.2, "four" },
1752                         { 96, 200, 0.3, "lines" }
1753                 });
1754         check_verify_result (
1755                 {dir},
1756                 {
1757                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1758                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1759                 });
1760 }
1761
1762
1763 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1764 {
1765         auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1766         auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1767                 dir,
1768                 {
1769                         { 96, 200, 0.0, "We" },
1770                         { 96, 200, 0.1, "have" },
1771                         { 96, 200, 0.2, "four" },
1772                 });
1773         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1774 }
1775
1776
1777 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1778 {
1779         auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1780         auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1781                 dir,
1782                 {
1783                         { 96, 300, 0.0, "We" },
1784                         { 96, 300, 0.1, "have" },
1785                         { 150, 180, 0.2, "four" },
1786                         { 150, 180, 0.3, "lines" }
1787                 });
1788         check_verify_result (
1789                 {dir},
1790                 {
1791                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1792                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1793                 });
1794 }
1795
1796
1797 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1798 {
1799         auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1800         auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1801                 dir,
1802                 {
1803                         { 96, 300, 0.0, "We" },
1804                         { 96, 300, 0.1, "have" },
1805                         { 150, 180, 0.2, "four" },
1806                         { 190, 250, 0.3, "lines" }
1807                 });
1808         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1809 }
1810
1811
1812 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1813 {
1814         auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1815         auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1816                 dir,
1817                 {
1818                         { 96, 300, 0.0, "0123456789012345678901234567890123" }
1819                 });
1820         check_verify_result (
1821                 {dir},
1822                 {
1823                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1824                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1825                 });
1826 }
1827
1828
1829 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1830 {
1831         path const dir("build/test/verify_invalid_sound_frame_rate");
1832         prepare_directory (dir);
1833
1834         auto picture = simple_picture (dir, "foo");
1835         auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1836         auto reel = make_shared<dcp::Reel>();
1837         reel->add (reel_picture);
1838         auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1839         auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1840         reel->add (reel_sound);
1841         reel->add (simple_markers());
1842         auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1843         cpl->add (reel);
1844         auto dcp = make_shared<dcp::DCP>(dir);
1845         dcp->add (cpl);
1846         dcp->write_xml (
1847                 dcp::Standard::SMPTE,
1848                 dcp::String::compose("libdcp %1", dcp::version),
1849                 dcp::String::compose("libdcp %1", dcp::version),
1850                 dcp::LocalTime().as_string(),
1851                 "hello"
1852                 );
1853
1854         check_verify_result (
1855                 {dir},
1856                 {
1857                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1858                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1859                 });
1860 }
1861
1862
1863 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1864 {
1865         path const dir("build/test/verify_missing_cpl_annotation_text");
1866         auto dcp = make_simple (dir);
1867         dcp->write_xml (
1868                 dcp::Standard::SMPTE,
1869                 dcp::String::compose("libdcp %1", dcp::version),
1870                 dcp::String::compose("libdcp %1", dcp::version),
1871                 dcp::LocalTime().as_string(),
1872                 "A Test DCP"
1873                 );
1874
1875         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1876
1877         auto const cpl = dcp->cpls()[0];
1878
1879         {
1880                 BOOST_REQUIRE (cpl->file());
1881                 Editor e(cpl->file().get());
1882                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1883         }
1884
1885         check_verify_result (
1886                 {dir},
1887                 {
1888                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1889                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1890                 });
1891 }
1892
1893
1894 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1895 {
1896         path const dir("build/test/verify_mismatched_cpl_annotation_text");
1897         auto dcp = make_simple (dir);
1898         dcp->write_xml (
1899                 dcp::Standard::SMPTE,
1900                 dcp::String::compose("libdcp %1", dcp::version),
1901                 dcp::String::compose("libdcp %1", dcp::version),
1902                 dcp::LocalTime().as_string(),
1903                 "A Test DCP"
1904                 );
1905
1906         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1907         auto const cpl = dcp->cpls()[0];
1908
1909         {
1910                 BOOST_REQUIRE (cpl->file());
1911                 Editor e(cpl->file().get());
1912                 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1913         }
1914
1915         check_verify_result (
1916                 {dir},
1917                 {
1918                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1919                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1920                 });
1921 }
1922
1923
1924 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1925 {
1926         path const dir("build/test/verify_mismatched_asset_duration");
1927         prepare_directory (dir);
1928         shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1929         shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1930
1931         shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1932         shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1933
1934         auto reel = make_shared<dcp::Reel>(
1935                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1936                 make_shared<dcp::ReelSoundAsset>(ms, 0)
1937                 );
1938
1939         reel->add (simple_markers());
1940         cpl->add (reel);
1941
1942         dcp->add (cpl);
1943         dcp->write_xml (
1944                 dcp::Standard::SMPTE,
1945                 dcp::String::compose("libdcp %1", dcp::version),
1946                 dcp::String::compose("libdcp %1", dcp::version),
1947                 dcp::LocalTime().as_string(),
1948                 "A Test DCP"
1949                 );
1950
1951         check_verify_result (
1952                 {dir},
1953                 {
1954                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1955                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1956                 });
1957 }
1958
1959
1960
1961 static
1962 shared_ptr<dcp::CPL>
1963 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1964 {
1965         prepare_directory (dir);
1966         auto dcp = make_shared<dcp::DCP>(dir);
1967         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1968
1969         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1970         subs->set_language (dcp::LanguageTag("de-DE"));
1971         subs->set_start_time (dcp::Time());
1972         subs->add (simple_subtitle());
1973         subs->write (dir / "subs.mxf");
1974         auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1975
1976         auto reel1 = make_shared<dcp::Reel>(
1977                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1978                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1979                 );
1980
1981         if (add_to_reel1) {
1982                 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1983         }
1984
1985         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1986         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1987         reel1->add (markers1);
1988
1989         cpl->add (reel1);
1990
1991         auto reel2 = make_shared<dcp::Reel>(
1992                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1993                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1994                 );
1995
1996         if (add_to_reel2) {
1997                 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1998         }
1999
2000         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2001         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2002         reel2->add (markers2);
2003
2004         cpl->add (reel2);
2005
2006         dcp->add (cpl);
2007         dcp->write_xml (
2008                 dcp::Standard::SMPTE,
2009                 dcp::String::compose("libdcp %1", dcp::version),
2010                 dcp::String::compose("libdcp %1", dcp::version),
2011                 dcp::LocalTime().as_string(),
2012                 "A Test DCP"
2013                 );
2014
2015         return cpl;
2016 }
2017
2018
2019 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2020 {
2021         {
2022                 path dir ("build/test/missing_main_subtitle_from_some_reels");
2023                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2024                 check_verify_result (
2025                         { dir },
2026                         {
2027                                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2028                                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2029                         });
2030
2031         }
2032
2033         {
2034                 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2035                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2036                 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2037         }
2038
2039         {
2040                 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2041                 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2042                 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2043         }
2044 }
2045
2046
2047 static
2048 shared_ptr<dcp::CPL>
2049 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2050 {
2051         prepare_directory (dir);
2052         auto dcp = make_shared<dcp::DCP>(dir);
2053         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2054
2055         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2056         subs->set_language (dcp::LanguageTag("de-DE"));
2057         subs->set_start_time (dcp::Time());
2058         subs->add (simple_subtitle());
2059         subs->write (dir / "subs.mxf");
2060
2061         auto reel1 = make_shared<dcp::Reel>(
2062                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2063                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2064                 );
2065
2066         for (int i = 0; i < caps_in_reel1; ++i) {
2067                 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2068         }
2069
2070         auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2071         markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2072         reel1->add (markers1);
2073
2074         cpl->add (reel1);
2075
2076         auto reel2 = make_shared<dcp::Reel>(
2077                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2078                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2079                 );
2080
2081         for (int i = 0; i < caps_in_reel2; ++i) {
2082                 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2083         }
2084
2085         auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2086         markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2087         reel2->add (markers2);
2088
2089         cpl->add (reel2);
2090
2091         dcp->add (cpl);
2092         dcp->write_xml (
2093                 dcp::Standard::SMPTE,
2094                 dcp::String::compose("libdcp %1", dcp::version),
2095                 dcp::String::compose("libdcp %1", dcp::version),
2096                 dcp::LocalTime().as_string(),
2097                 "A Test DCP"
2098                 );
2099
2100         return cpl;
2101 }
2102
2103
2104 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2105 {
2106         {
2107                 path dir ("build/test/mismatched_closed_caption_asset_counts");
2108                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2109                 check_verify_result (
2110                         {dir},
2111                         {
2112                                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2113                                 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2114                         });
2115         }
2116
2117         {
2118                 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2119                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2120                 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2121         }
2122
2123         {
2124                 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2125                 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2126                 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2127         }
2128 }
2129
2130
2131 template <class T>
2132 void
2133 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2134 {
2135         prepare_directory (dir);
2136         auto dcp = make_shared<dcp::DCP>(dir);
2137         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2138
2139         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2140         subs->set_language (dcp::LanguageTag("de-DE"));
2141         subs->set_start_time (dcp::Time());
2142         subs->add (simple_subtitle());
2143         subs->write (dir / "subs.mxf");
2144         auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2145         adjust (reel_text);
2146
2147         auto reel = make_shared<dcp::Reel>(
2148                 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2149                 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2150                 );
2151
2152         reel->add (reel_text);
2153
2154         reel->add (simple_markers(240));
2155
2156         cpl->add (reel);
2157
2158         dcp->add (cpl);
2159         dcp->write_xml (
2160                 dcp::Standard::SMPTE,
2161                 dcp::String::compose("libdcp %1", dcp::version),
2162                 dcp::String::compose("libdcp %1", dcp::version),
2163                 dcp::LocalTime().as_string(),
2164                 "A Test DCP"
2165                 );
2166
2167         check_verify_result (
2168                 {dir},
2169                 {
2170                         { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2171                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2172                 });
2173 }
2174
2175
2176 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2177 {
2178         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2179                 "build/test/verify_subtitle_entry_point_must_be_present",
2180                 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2181                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2182                         asset->unset_entry_point ();
2183                         }
2184                 );
2185
2186         verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2187                 "build/test/verify_subtitle_entry_point_must_be_zero",
2188                 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2189                 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2190                         asset->set_entry_point (4);
2191                         }
2192                 );
2193
2194         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2195                 "build/test/verify_closed_caption_entry_point_must_be_present",
2196                 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2197                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2198                         asset->unset_entry_point ();
2199                         }
2200                 );
2201
2202         verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2203                 "build/test/verify_closed_caption_entry_point_must_be_zero",
2204                 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2205                 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2206                         asset->set_entry_point (9);
2207                         }
2208                 );
2209 }
2210
2211
2212 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2213 {
2214         RNGFixer fix;
2215
2216         path const dir("build/test/verify_missing_hash");
2217         auto dcp = make_simple (dir);
2218         dcp->write_xml (
2219                 dcp::Standard::SMPTE,
2220                 dcp::String::compose("libdcp %1", dcp::version),
2221                 dcp::String::compose("libdcp %1", dcp::version),
2222                 dcp::LocalTime().as_string(),
2223                 "A Test DCP"
2224                 );
2225
2226         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2227         auto const cpl = dcp->cpls()[0];
2228
2229         {
2230                 BOOST_REQUIRE (cpl->file());
2231                 Editor e(cpl->file().get());
2232                 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2233         }
2234
2235         check_verify_result (
2236                 {dir},
2237                 {
2238                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2239                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2240                 });
2241 }
2242
2243
2244 static
2245 void
2246 verify_markers_test (
2247         path dir,
2248         vector<pair<dcp::Marker, dcp::Time>> markers,
2249         vector<dcp::VerificationNote> test_notes
2250         )
2251 {
2252         auto dcp = make_simple (dir);
2253         dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2254         auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2255         for (auto const& i: markers) {
2256                 markers_asset->set (i.first, i.second);
2257         }
2258         dcp->cpls()[0]->reels()[0]->add(markers_asset);
2259         dcp->write_xml (
2260                 dcp::Standard::SMPTE,
2261                 dcp::String::compose("libdcp %1", dcp::version),
2262                 dcp::String::compose("libdcp %1", dcp::version),
2263                 dcp::LocalTime().as_string(),
2264                 "A Test DCP"
2265                 );
2266
2267         check_verify_result ({dir}, test_notes);
2268 }
2269
2270
2271 BOOST_AUTO_TEST_CASE (verify_markers)
2272 {
2273         verify_markers_test (
2274                 "build/test/verify_markers_all_correct",
2275                 {
2276                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2277                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2278                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2279                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2280                 },
2281                 {}
2282                 );
2283
2284         verify_markers_test (
2285                 "build/test/verify_markers_missing_ffec",
2286                 {
2287                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2288                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2289                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2290                 },
2291                 {
2292                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2293                 });
2294
2295         verify_markers_test (
2296                 "build/test/verify_markers_missing_ffmc",
2297                 {
2298                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2299                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2300                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2301                 },
2302                 {
2303                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2304                 });
2305
2306         verify_markers_test (
2307                 "build/test/verify_markers_missing_ffoc",
2308                 {
2309                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2310                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2311                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2312                 },
2313                 {
2314                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2315                 });
2316
2317         verify_markers_test (
2318                 "build/test/verify_markers_missing_lfoc",
2319                 {
2320                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2321                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2322                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2323                 },
2324                 {
2325                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2326                 });
2327
2328         verify_markers_test (
2329                 "build/test/verify_markers_incorrect_ffoc",
2330                 {
2331                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2332                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2333                         { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2334                         { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2335                 },
2336                 {
2337                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2338                 });
2339
2340         verify_markers_test (
2341                 "build/test/verify_markers_incorrect_lfoc",
2342                 {
2343                         { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2344                         { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2345                         { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2346                         { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2347                 },
2348                 {
2349                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2350                 });
2351 }
2352
2353
2354 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2355 {
2356         path dir = "build/test/verify_missing_cpl_metadata_version_number";
2357         prepare_directory (dir);
2358         auto dcp = make_simple (dir);
2359         auto cpl = dcp->cpls()[0];
2360         cpl->unset_version_number();
2361         dcp->write_xml (
2362                 dcp::Standard::SMPTE,
2363                 dcp::String::compose("libdcp %1", dcp::version),
2364                 dcp::String::compose("libdcp %1", dcp::version),
2365                 dcp::LocalTime().as_string(),
2366                 "A Test DCP"
2367                 );
2368
2369         check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2370 }
2371
2372
2373 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2374 {
2375         path dir = "build/test/verify_missing_extension_metadata1";
2376         auto dcp = make_simple (dir);
2377         dcp->write_xml (
2378                 dcp::Standard::SMPTE,
2379                 dcp::String::compose("libdcp %1", dcp::version),
2380                 dcp::String::compose("libdcp %1", dcp::version),
2381                 dcp::LocalTime().as_string(),
2382                 "A Test DCP"
2383                 );
2384
2385         auto cpl = dcp->cpls()[0];
2386
2387         {
2388                 Editor e (cpl->file().get());
2389                 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2390         }
2391
2392         check_verify_result (
2393                 {dir},
2394                 {
2395                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2396                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2397                 });
2398 }
2399
2400
2401 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2402 {
2403         path dir = "build/test/verify_missing_extension_metadata2";
2404         auto dcp = make_simple (dir);
2405         dcp->write_xml (
2406                 dcp::Standard::SMPTE,
2407                 dcp::String::compose("libdcp %1", dcp::version),
2408                 dcp::String::compose("libdcp %1", dcp::version),
2409                 dcp::LocalTime().as_string(),
2410                 "A Test DCP"
2411                 );
2412
2413         auto cpl = dcp->cpls()[0];
2414
2415         {
2416                 Editor e (cpl->file().get());
2417                 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2418         }
2419
2420         check_verify_result (
2421                 {dir},
2422                 {
2423                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2424                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2425                 });
2426 }
2427
2428
2429 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2430 {
2431         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2432         auto dcp = make_simple (dir);
2433         dcp->write_xml (
2434                 dcp::Standard::SMPTE,
2435                 dcp::String::compose("libdcp %1", dcp::version),
2436                 dcp::String::compose("libdcp %1", dcp::version),
2437                 dcp::LocalTime().as_string(),
2438                 "A Test DCP"
2439                 );
2440
2441         auto const cpl = dcp->cpls()[0];
2442
2443         {
2444                 Editor e (cpl->file().get());
2445                 e.replace ("<meta:Name>A", "<meta:NameX>A");
2446                 e.replace ("n</meta:Name>", "n</meta:NameX>");
2447         }
2448
2449         check_verify_result (
2450                 {dir},
2451                 {
2452                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2453                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2454                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2455                 });
2456 }
2457
2458
2459 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2460 {
2461         path dir = "build/test/verify_invalid_extension_metadata1";
2462         auto dcp = make_simple (dir);
2463         dcp->write_xml (
2464                 dcp::Standard::SMPTE,
2465                 dcp::String::compose("libdcp %1", dcp::version),
2466                 dcp::String::compose("libdcp %1", dcp::version),
2467                 dcp::LocalTime().as_string(),
2468                 "A Test DCP"
2469                 );
2470
2471         auto cpl = dcp->cpls()[0];
2472
2473         {
2474                 Editor e (cpl->file().get());
2475                 e.replace ("Application", "Fred");
2476         }
2477
2478         check_verify_result (
2479                 {dir},
2480                 {
2481                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2482                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2483                 });
2484 }
2485
2486
2487 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2488 {
2489         path dir = "build/test/verify_invalid_extension_metadata2";
2490         auto dcp = make_simple (dir);
2491         dcp->write_xml (
2492                 dcp::Standard::SMPTE,
2493                 dcp::String::compose("libdcp %1", dcp::version),
2494                 dcp::String::compose("libdcp %1", dcp::version),
2495                 dcp::LocalTime().as_string(),
2496                 "A Test DCP"
2497                 );
2498
2499         auto cpl = dcp->cpls()[0];
2500
2501         {
2502                 Editor e (cpl->file().get());
2503                 e.replace ("DCP Constraints Profile", "Fred");
2504         }
2505
2506         check_verify_result (
2507                 {dir},
2508                 {
2509                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2510                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2511                 });
2512 }
2513
2514
2515 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2516 {
2517         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2518         auto dcp = make_simple (dir);
2519         dcp->write_xml (
2520                 dcp::Standard::SMPTE,
2521                 dcp::String::compose("libdcp %1", dcp::version),
2522                 dcp::String::compose("libdcp %1", dcp::version),
2523                 dcp::LocalTime().as_string(),
2524                 "A Test DCP"
2525                 );
2526
2527         auto const cpl = dcp->cpls()[0];
2528
2529         {
2530                 Editor e (cpl->file().get());
2531                 e.replace ("<meta:Value>", "<meta:ValueX>");
2532                 e.replace ("</meta:Value>", "</meta:ValueX>");
2533         }
2534
2535         check_verify_result (
2536                 {dir},
2537                 {
2538                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2539                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 },
2540                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2541                 });
2542 }
2543
2544
2545 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2546 {
2547         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2548         auto dcp = make_simple (dir);
2549         dcp->write_xml (
2550                 dcp::Standard::SMPTE,
2551                 dcp::String::compose("libdcp %1", dcp::version),
2552                 dcp::String::compose("libdcp %1", dcp::version),
2553                 dcp::LocalTime().as_string(),
2554                 "A Test DCP"
2555                 );
2556
2557         auto const cpl = dcp->cpls()[0];
2558
2559         {
2560                 Editor e (cpl->file().get());
2561                 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2562         }
2563
2564         check_verify_result (
2565                 {dir},
2566                 {
2567                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2568                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2569                 });
2570 }
2571
2572
2573 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2574 {
2575         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2576         auto dcp = make_simple (dir);
2577         dcp->write_xml (
2578                 dcp::Standard::SMPTE,
2579                 dcp::String::compose("libdcp %1", dcp::version),
2580                 dcp::String::compose("libdcp %1", dcp::version),
2581                 dcp::LocalTime().as_string(),
2582                 "A Test DCP"
2583                 );
2584
2585         auto const cpl = dcp->cpls()[0];
2586
2587         {
2588                 Editor e (cpl->file().get());
2589                 e.replace ("<meta:Property>", "<meta:PropertyX>");
2590                 e.replace ("</meta:Property>", "</meta:PropertyX>");
2591         }
2592
2593         check_verify_result (
2594                 {dir},
2595                 {
2596                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2597                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2598                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2599                 });
2600 }
2601
2602
2603 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2604 {
2605         path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2606         auto dcp = make_simple (dir);
2607         dcp->write_xml (
2608                 dcp::Standard::SMPTE,
2609                 dcp::String::compose("libdcp %1", dcp::version),
2610                 dcp::String::compose("libdcp %1", dcp::version),
2611                 dcp::LocalTime().as_string(),
2612                 "A Test DCP"
2613                 );
2614
2615         auto const cpl = dcp->cpls()[0];
2616
2617         {
2618                 Editor e (cpl->file().get());
2619                 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2620                 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2621         }
2622
2623         check_verify_result (
2624                 {dir},
2625                 {
2626                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2627                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2628                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2629                 });
2630 }
2631
2632
2633
2634 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2635 {
2636         path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2637         prepare_directory (dir);
2638         for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2639                 copy_file (i.path(), dir / i.path().filename());
2640         }
2641
2642         string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2643         path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2644         string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2645         path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2646
2647         {
2648                 Editor e (cpl);
2649                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2650         }
2651
2652         check_verify_result (
2653                 {dir},
2654                 {
2655                         { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2656                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2657                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2658                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2659                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2660                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2661                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2662                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2663                 });
2664 }
2665
2666
2667 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2668 {
2669         path dir = "build/test/unsigned_pkl_with_encrypted_content";
2670         prepare_directory (dir);
2671         for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2672                 copy_file (i.path(), dir / i.path().filename());
2673         }
2674
2675         string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2676         path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2677         string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2678         path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2679         {
2680                 Editor e (pkl);
2681                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2682         }
2683
2684         check_verify_result (
2685                 {dir},
2686                 {
2687                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2688                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2689                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2690                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2691                         { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2692                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2693                         { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2694                 });
2695 }
2696
2697
2698 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2699 {
2700         path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2701         prepare_directory (dir);
2702         for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2703                 copy_file (i.path(), dir / i.path().filename());
2704         }
2705
2706         {
2707                 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2708                 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2709         }
2710
2711         check_verify_result ({dir}, {});
2712 }
2713
2714
2715 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2716 {
2717         path dir ("build/test/verify_must_not_be_partially_encrypted");
2718         prepare_directory (dir);
2719
2720         dcp::DCP d (dir);
2721
2722         auto signer = make_shared<dcp::CertificateChain>();
2723         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2724         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2725         signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2726         signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2727
2728         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2729
2730         dcp::Key key;
2731
2732         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2733         mp->set_key (key);
2734
2735         auto writer = mp->start_write (dir / "video.mxf", false);
2736         dcp::ArrayData j2c ("test/data/flat_red.j2c");
2737         for (int i = 0; i < 24; ++i) {
2738                 writer->write (j2c.data(), j2c.size());
2739         }
2740         writer->finalize ();
2741
2742         auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2743
2744         auto reel = make_shared<dcp::Reel>(
2745                 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2746                 make_shared<dcp::ReelSoundAsset>(ms, 0)
2747                 );
2748
2749         reel->add (simple_markers());
2750
2751         cpl->add (reel);
2752
2753         cpl->set_content_version (
2754                 {"urn:uri:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00", "81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00"}
2755                 );
2756         cpl->set_annotation_text ("A Test DCP");
2757         cpl->set_issuer ("OpenDCP 0.0.25");
2758         cpl->set_creator ("OpenDCP 0.0.25");
2759         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2760         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2761         cpl->set_main_sound_sample_rate (48000);
2762         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2763         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2764         cpl->set_version_number (1);
2765
2766         d.add (cpl);
2767
2768         d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2769
2770         check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});
2771 }
2772
2773
2774 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2775 {
2776         vector<dcp::VerificationNote> notes;
2777         dcp::MonoPictureAsset picture (find_file(private_test / "data" / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV", "j2c.mxf"));
2778         auto reader = picture.start_read ();
2779         auto frame = reader->get_frame (0);
2780         verify_j2k (frame, notes);
2781         dump_notes (notes);
2782 }
2783
2784
2785 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2786 {
2787         vector<dcp::VerificationNote> notes;
2788         dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2789         auto reader = picture.start_read ();
2790         auto frame = reader->get_frame (0);
2791         verify_j2k (frame, notes);
2792         dump_notes (notes);
2793 }
2794
2795
2796 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2797 {
2798         boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2799         prepare_directory (dir);
2800         auto dcp = make_simple (dir);
2801         dcp->write_xml (dcp::Standard::SMPTE);
2802         vector<dcp::VerificationNote> notes;
2803         dcp::MonoPictureAsset picture (find_file(dir, "video"));
2804         auto reader = picture.start_read ();
2805         auto frame = reader->get_frame (0);
2806         verify_j2k (frame, notes);
2807         dump_notes (notes);
2808 }
2809