2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
38 #include "reel_mono_picture_asset.h"
39 #include "reel_sound_asset.h"
42 #include "openjpeg_image.h"
43 #include "mono_picture_asset.h"
44 #include "stereo_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "interop_subtitle_asset.h"
47 #include "smpte_subtitle_asset.h"
48 #include "reel_closed_caption_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_subtitle_asset.h"
51 #include "reel_markers_asset.h"
52 #include "compose.hpp"
54 #include <boost/test/unit_test.hpp>
55 #include <boost/foreach.hpp>
56 #include <boost/algorithm/string.hpp>
65 using std::make_shared;
66 using boost::optional;
67 using namespace boost::filesystem;
68 using std::shared_ptr;
71 static list<pair<string, optional<path>>> stages;
72 static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml";
73 static string const dcp_test1_cpl = "cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
76 stage (string s, optional<path> p)
78 stages.push_back (make_pair (s, p));
88 prepare_directory (path path)
90 using namespace boost::filesystem;
92 create_directories (path);
97 setup (int reference_number, int verify_test_number)
99 prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
100 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
101 copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename());
104 return { dcp::String::compose("build/test/verify_test%1", verify_test_number) };
111 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::SMPTE)
113 auto reel = make_shared<dcp::Reel>();
114 reel->add (reel_asset);
115 reel->add (simple_markers());
117 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
119 auto dcp = make_shared<dcp::DCP>(dir);
123 dcp::String::compose("libdcp %1", dcp::version),
124 dcp::String::compose("libdcp %1", dcp::version),
125 dcp::LocalTime().as_string(),
131 /** Class that can alter a file by searching and replacing strings within it.
132 * On destruction modifies the file whose name was given to the constructor.
140 _content = dcp::file_to_string (_path);
145 auto f = fopen(_path.string().c_str(), "w");
147 fwrite (_content.c_str(), _content.length(), 1, f);
151 void replace (string a, string b)
153 auto old_content = _content;
154 boost::algorithm::replace_all (_content, a, b);
155 BOOST_REQUIRE (_content != old_content);
158 void delete_lines (string from, string to)
160 vector<string> lines;
161 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
162 bool deleting = false;
163 auto old_content = _content;
165 for (auto i: lines) {
166 if (i.find(from) != string::npos) {
170 _content += i + "\n";
172 if (deleting && i.find(to) != string::npos) {
176 BOOST_REQUIRE (_content != old_content);
181 std::string _content;
187 dump_notes (vector<dcp::VerificationNote> const & notes)
189 for (auto i: notes) {
190 std::cout << dcp::note_to_string(i) << "\n";
197 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
199 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
201 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
202 for (auto i = 0U; i < notes.size(); ++i) {
203 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
210 check_verify_result_after_replace (int n, boost::function<path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
212 auto directories = setup (1, n);
216 e.replace (from, to);
219 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
221 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
222 auto i = notes.begin();
223 auto j = codes.begin();
224 while (i != notes.end()) {
225 BOOST_CHECK_EQUAL (i->code(), *j);
232 /* Check DCP as-is (should be OK) */
233 BOOST_AUTO_TEST_CASE (verify_test1)
236 auto directories = setup (1, 1);
237 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
239 path const cpl_file = path("build") / "test" / "verify_test1" / dcp_test1_cpl;
240 path const pkl_file = path("build") / "test" / "verify_test1" / dcp_test1_pkl;
241 path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
243 auto st = stages.begin();
244 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
245 BOOST_REQUIRE (st->second);
246 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test1"));
248 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
249 BOOST_REQUIRE (st->second);
250 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
252 BOOST_CHECK_EQUAL (st->first, "Checking reel");
253 BOOST_REQUIRE (!st->second);
255 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
256 BOOST_REQUIRE (st->second);
257 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test1/video.mxf"));
259 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
260 BOOST_REQUIRE (st->second);
261 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test1/video.mxf"));
263 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
264 BOOST_REQUIRE (st->second);
265 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test1/audio.mxf"));
267 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
268 BOOST_REQUIRE (st->second);
269 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test1/audio.mxf"));
271 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
272 BOOST_REQUIRE (st->second);
273 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
275 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
276 BOOST_REQUIRE (st->second);
277 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
279 BOOST_REQUIRE (st == stages.end());
281 BOOST_CHECK_EQUAL (notes.size(), 0);
284 /* Corrupt the MXFs and check that this is spotted */
285 BOOST_AUTO_TEST_CASE (verify_test2)
287 using namespace boost::filesystem;
289 auto directories = setup (1, 2);
291 auto video_path = path("build/test/verify_test2/video.mxf");
292 auto mod = fopen(video_path.string().c_str(), "r+b");
294 fseek (mod, 4096, SEEK_SET);
296 fwrite (&x, sizeof(x), 1, mod);
299 auto audio_path = path("build/test/verify_test2/audio.mxf");
300 mod = fopen(audio_path.string().c_str(), "r+b");
302 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
303 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
306 dcp::ASDCPErrorSuspender sus;
307 check_verify_result (
310 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_PICTURE_HASH, canonical(video_path) },
311 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_SOUND_HASH, canonical(audio_path) },
315 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
316 BOOST_AUTO_TEST_CASE (verify_test3)
318 using namespace boost::filesystem;
320 auto directories = setup (1, 3);
322 path const dir = path("build") / "test" / "verify_test3";
325 Editor e (dir / dcp_test1_pkl);
326 e.replace ("<Hash>", "<Hash>x");
329 check_verify_result (
332 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
333 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
334 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
335 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xxz+gUPoPMdbFlAewvWIq8BRhBmA=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
336 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xXGhFVrqZqapOJx5Fh2SLjj48Yjg=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
337 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
341 /* Corrupt the ContentKind in the CPL */
342 BOOST_AUTO_TEST_CASE (verify_test4)
344 auto directories = setup (1, 4);
347 Editor e (path("build") / "test" / "verify_test4" / dcp_test1_cpl);
348 e.replace ("<ContentKind>", "<ContentKind>x");
351 check_verify_result (
353 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("Bad content kind 'xtrailer'")}}
361 return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_cpl);
368 return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_pkl);
375 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
380 BOOST_AUTO_TEST_CASE (verify_test5)
382 check_verify_result_after_replace (
384 "<FrameRate>24 1", "<FrameRate>99 1",
385 { dcp::VerificationNote::MISMATCHED_CPL_HASHES,
386 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
391 BOOST_AUTO_TEST_CASE (verify_test6)
393 auto directories = setup (1, 6);
395 path dir = "build/test/verify_test6";
396 remove (dir / "video.mxf");
397 check_verify_result (
400 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET, canonical(dir) / "video.mxf" }
408 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
411 /* Empty asset filename in ASSETMAP */
412 BOOST_AUTO_TEST_CASE (verify_test7)
414 check_verify_result_after_replace (
416 "<Path>video.mxf</Path>", "<Path></Path>",
417 { dcp::VerificationNote::EMPTY_ASSET_PATH }
421 /* Mismatched standard */
422 BOOST_AUTO_TEST_CASE (verify_test8)
424 check_verify_result_after_replace (
426 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
427 { dcp::VerificationNote::MISMATCHED_STANDARD,
428 dcp::VerificationNote::INVALID_XML,
429 dcp::VerificationNote::INVALID_XML,
430 dcp::VerificationNote::INVALID_XML,
431 dcp::VerificationNote::INVALID_XML,
432 dcp::VerificationNote::INVALID_XML,
433 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
437 /* Badly formatted <Id> in CPL */
438 BOOST_AUTO_TEST_CASE (verify_test9)
440 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
441 check_verify_result_after_replace (
443 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
444 { dcp::VerificationNote::INVALID_XML }
448 /* Badly formatted <IssueDate> in CPL */
449 BOOST_AUTO_TEST_CASE (verify_test10)
451 check_verify_result_after_replace (
453 "<IssueDate>", "<IssueDate>x",
454 { dcp::VerificationNote::INVALID_XML,
455 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
459 /* Badly-formatted <Id> in PKL */
460 BOOST_AUTO_TEST_CASE (verify_test11)
462 check_verify_result_after_replace (
464 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
465 { dcp::VerificationNote::INVALID_XML }
469 /* Badly-formatted <Id> in ASSETMAP */
470 BOOST_AUTO_TEST_CASE (verify_test12)
472 check_verify_result_after_replace (
474 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
475 { dcp::VerificationNote::INVALID_XML }
479 /* Basic test of an Interop DCP */
480 BOOST_AUTO_TEST_CASE (verify_test13)
483 auto directories = setup (3, 13);
484 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
486 path const cpl_file = path("build") / "test" / "verify_test13" / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
487 path const pkl_file = path("build") / "test" / "verify_test13" / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
488 path const assetmap_file = "build/test/verify_test13/ASSETMAP";
490 auto st = stages.begin();
491 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
492 BOOST_REQUIRE (st->second);
493 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13"));
495 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
496 BOOST_REQUIRE (st->second);
497 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
499 BOOST_CHECK_EQUAL (st->first, "Checking reel");
500 BOOST_REQUIRE (!st->second);
502 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
503 BOOST_REQUIRE (st->second);
504 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
506 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
507 BOOST_REQUIRE (st->second);
508 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
510 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
511 BOOST_REQUIRE (st->second);
512 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
514 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
515 BOOST_REQUIRE (st->second);
516 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
518 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
519 BOOST_REQUIRE (st->second);
520 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
522 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
523 BOOST_REQUIRE (st->second);
524 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
526 BOOST_REQUIRE (st == stages.end());
528 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
529 auto i = notes.begin ();
530 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
531 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INVALID_STANDARD);
534 /* DCP with a short asset */
535 BOOST_AUTO_TEST_CASE (verify_test14)
537 auto directories = setup (8, 14);
538 check_verify_result (
541 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
542 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
543 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
544 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
545 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
552 dcp_from_frame (dcp::ArrayData const& frame, path dir)
554 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
555 create_directories (dir);
556 auto writer = asset->start_write (dir / "pic.mxf", true);
557 for (int i = 0; i < 24; ++i) {
558 writer->write (frame.data(), frame.size());
562 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
563 write_dcp_with_single_asset (dir, reel_asset);
567 /* DCP with an over-sized JPEG2000 frame */
568 BOOST_AUTO_TEST_CASE (verify_test15)
570 int const too_big = 1302083 * 2;
572 /* Compress a black image */
573 auto image = black_image ();
574 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
575 BOOST_REQUIRE (frame.size() < too_big);
577 /* Place it in a bigger block with some zero padding at the end */
578 dcp::ArrayData oversized_frame(too_big);
579 memcpy (oversized_frame.data(), frame.data(), frame.size());
580 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
582 path const dir("build/test/verify_test15");
583 prepare_directory (dir);
584 dcp_from_frame (oversized_frame, dir);
586 check_verify_result (
589 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
590 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
595 /* DCP with a nearly over-sized JPEG2000 frame */
596 BOOST_AUTO_TEST_CASE (verify_test16)
598 int const nearly_too_big = 1302083 * 0.98;
600 /* Compress a black image */
601 auto image = black_image ();
602 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
603 BOOST_REQUIRE (frame.size() < nearly_too_big);
605 /* Place it in a bigger block with some zero padding at the end */
606 dcp::ArrayData oversized_frame(nearly_too_big);
607 memcpy (oversized_frame.data(), frame.data(), frame.size());
608 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
610 path const dir("build/test/verify_test16");
611 prepare_directory (dir);
612 dcp_from_frame (oversized_frame, dir);
614 check_verify_result (
617 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
618 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
623 /* DCP with a within-range JPEG2000 frame */
624 BOOST_AUTO_TEST_CASE (verify_test17)
626 /* Compress a black image */
627 auto image = black_image ();
628 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
629 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
631 path const dir("build/test/verify_test17");
632 prepare_directory (dir);
633 dcp_from_frame (frame, dir);
635 check_verify_result ({ dir }, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
639 /* DCP with valid Interop subtitles */
640 BOOST_AUTO_TEST_CASE (verify_test18)
642 path const dir("build/test/verify_test18");
643 prepare_directory (dir);
644 copy_file ("test/data/subs1.xml", dir / "subs.xml");
645 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
646 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
647 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
649 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD }});
653 /* DCP with broken Interop subtitles */
654 BOOST_AUTO_TEST_CASE (verify_test19)
656 using namespace boost::filesystem;
658 path const dir("build/test/verify_test19");
659 prepare_directory (dir);
660 copy_file ("test/data/subs1.xml", dir / "subs.xml");
661 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
662 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
663 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
666 Editor e (dir / "subs.xml");
667 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
670 check_verify_result (
673 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
674 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
676 dcp::VerificationNote::VERIFY_ERROR,
677 dcp::VerificationNote::INVALID_XML,
678 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
686 /* DCP with valid SMPTE subtitles */
687 BOOST_AUTO_TEST_CASE (verify_test20)
689 path const dir("build/test/verify_test20");
690 prepare_directory (dir);
691 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
692 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
693 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
694 write_dcp_with_single_asset (dir, reel_asset);
696 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
700 /* DCP with broken SMPTE subtitles */
701 BOOST_AUTO_TEST_CASE (verify_test21)
703 using namespace boost::filesystem;
705 path const dir("build/test/verify_test21");
706 prepare_directory (dir);
707 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
708 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
709 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
710 write_dcp_with_single_asset (dir, reel_asset);
712 check_verify_result (
715 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
717 dcp::VerificationNote::VERIFY_ERROR,
718 dcp::VerificationNote::INVALID_XML,
719 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
723 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
724 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
730 BOOST_AUTO_TEST_CASE (verify_test22)
732 path const ov_dir("build/test/verify_test22_ov");
733 prepare_directory (ov_dir);
735 auto image = black_image ();
736 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
737 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
738 dcp_from_frame (frame, ov_dir);
740 dcp::DCP ov (ov_dir);
743 path const vf_dir("build/test/verify_test22_vf");
744 prepare_directory (vf_dir);
746 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
747 write_dcp_with_single_asset (vf_dir, picture);
749 check_verify_result (
752 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET, picture->asset()->id() },
753 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
758 /* DCP with valid CompositionMetadataAsset */
759 BOOST_AUTO_TEST_CASE (verify_test23)
761 path const dir("build/test/verify_test23");
762 prepare_directory (dir);
764 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
765 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
766 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
768 auto reel = make_shared<dcp::Reel>();
769 reel->add (reel_asset);
771 reel->add (simple_markers(16 * 24 - 1));
773 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
775 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
776 cpl->set_main_sound_sample_rate (48000);
777 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
778 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
784 dcp::String::compose("libdcp %1", dcp::version),
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::LocalTime().as_string(),
790 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
794 path find_cpl (path dir)
796 for (auto i: directory_iterator(dir)) {
797 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
802 BOOST_REQUIRE (false);
807 /* DCP with invalid CompositionMetadataAsset */
808 BOOST_AUTO_TEST_CASE (verify_test24)
810 using namespace boost::filesystem;
812 path const dir("build/test/verify_test24");
813 prepare_directory (dir);
815 auto reel = make_shared<dcp::Reel>();
816 reel->add (black_picture_asset(dir));
817 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
819 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
820 cpl->set_main_sound_sample_rate (48000);
821 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
822 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
823 cpl->set_version_number (1);
825 reel->add (simple_markers());
831 dcp::String::compose("libdcp %1", dcp::version),
832 dcp::String::compose("libdcp %1", dcp::version),
833 dcp::LocalTime().as_string(),
838 Editor e (find_cpl("build/test/verify_test24"));
839 e.replace ("MainSound", "MainSoundX");
842 check_verify_result (
845 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
846 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
848 dcp::VerificationNote::VERIFY_ERROR,
849 dcp::VerificationNote::INVALID_XML,
850 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
851 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
852 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
853 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
854 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
855 "ExtensionMetadataList?,)'"),
856 canonical(cpl->file().get()),
859 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
864 /* DCP with invalid CompositionMetadataAsset */
865 BOOST_AUTO_TEST_CASE (verify_test25)
867 path const dir("build/test/verify_test25");
868 prepare_directory (dir);
870 auto reel = make_shared<dcp::Reel>();
871 reel->add (black_picture_asset(dir));
872 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
874 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
875 cpl->set_main_sound_sample_rate (48000);
876 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
877 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
883 dcp::String::compose("libdcp %1", dcp::version),
884 dcp::String::compose("libdcp %1", dcp::version),
885 dcp::LocalTime().as_string(),
890 Editor e (find_cpl("build/test/verify_test25"));
891 e.replace ("meta:Width", "meta:WidthX");
894 check_verify_result (
896 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
901 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
902 BOOST_AUTO_TEST_CASE (verify_test26)
904 path const dir("build/test/verify_test26");
905 prepare_directory (dir);
906 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
907 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
908 asset->_language = "wrong-andbad";
909 asset->write (dir / "subs.mxf");
910 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
911 reel_asset->_language = "badlang";
912 write_dcp_with_single_asset (dir, reel_asset);
914 check_verify_result (
917 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
918 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
919 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
924 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
925 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
927 path const dir("build/test/verify_invalid_closed_caption_languages");
928 prepare_directory (dir);
929 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
930 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
931 asset->_language = "wrong-andbad";
932 asset->write (dir / "subs.mxf");
933 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
934 reel_asset->_language = "badlang";
935 write_dcp_with_single_asset (dir, reel_asset);
937 check_verify_result (
940 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
941 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
942 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
947 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
948 * the release territory.
950 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
952 path const dir("build/test/verify_various_invalid_languages");
953 prepare_directory (dir);
955 auto picture = simple_picture (dir, "foo");
956 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
957 auto reel = make_shared<dcp::Reel>();
958 reel->add (reel_picture);
959 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
960 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
961 reel->add (reel_sound);
962 reel->add (simple_markers());
964 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
966 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
967 cpl->_additional_subtitle_languages.push_back("andso-is-this");
968 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
969 cpl->set_main_sound_sample_rate (48000);
970 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
971 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
972 cpl->set_version_number (1);
973 cpl->_release_territory = "fred-jim";
974 auto dcp = make_shared<dcp::DCP>(dir);
978 dcp::String::compose("libdcp %1", dcp::version),
979 dcp::String::compose("libdcp %1", dcp::version),
980 dcp::LocalTime().as_string(),
984 check_verify_result (
987 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("this-is-wrong") },
988 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("andso-is-this") },
989 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("fred-jim") },
990 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("frobozz") },
996 vector<dcp::VerificationNote>
997 check_picture_size (int width, int height, int frame_rate, bool three_d)
999 using namespace boost::filesystem;
1001 path dcp_path = "build/test/verify_picture_test";
1002 prepare_directory (dcp_path);
1004 shared_ptr<dcp::PictureAsset> mp;
1006 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
1008 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
1010 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1012 auto image = black_image (dcp::Size(width, height));
1013 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1014 int const length = three_d ? frame_rate * 2 : frame_rate;
1015 for (int i = 0; i < length; ++i) {
1016 picture_writer->write (j2c.data(), j2c.size());
1018 picture_writer->finalize ();
1020 auto d = make_shared<dcp::DCP>(dcp_path);
1021 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1022 cpl->set_annotation_text ("A Test DCP");
1023 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1024 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1025 cpl->set_main_sound_sample_rate (48000);
1026 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1027 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1028 cpl->set_version_number (1);
1030 auto reel = make_shared<dcp::Reel>();
1033 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1035 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1038 reel->add (simple_markers(frame_rate));
1045 dcp::String::compose("libdcp %1", dcp::version),
1046 dcp::String::compose("libdcp %1", dcp::version),
1047 dcp::LocalTime().as_string(),
1051 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1057 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1059 auto notes = check_picture_size(width, height, frame_rate, three_d);
1061 BOOST_CHECK_EQUAL (notes.size(), 0U);
1067 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1069 auto notes = check_picture_size(width, height, frame_rate, three_d);
1070 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1071 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1072 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_SIZE_IN_PIXELS);
1078 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1080 auto notes = check_picture_size(width, height, frame_rate, three_d);
1081 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1082 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1083 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1089 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1091 auto notes = check_picture_size(width, height, frame_rate, three_d);
1092 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1093 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1094 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1098 BOOST_AUTO_TEST_CASE (verify_picture_size)
1100 using namespace boost::filesystem;
1103 check_picture_size_ok (2048, 858, 24, false);
1104 check_picture_size_ok (2048, 858, 25, false);
1105 check_picture_size_ok (2048, 858, 48, false);
1106 check_picture_size_ok (2048, 858, 24, true);
1107 check_picture_size_ok (2048, 858, 25, true);
1108 check_picture_size_ok (2048, 858, 48, true);
1111 check_picture_size_ok (1998, 1080, 24, false);
1112 check_picture_size_ok (1998, 1080, 25, false);
1113 check_picture_size_ok (1998, 1080, 48, false);
1114 check_picture_size_ok (1998, 1080, 24, true);
1115 check_picture_size_ok (1998, 1080, 25, true);
1116 check_picture_size_ok (1998, 1080, 48, true);
1119 check_picture_size_ok (4096, 1716, 24, false);
1122 check_picture_size_ok (3996, 2160, 24, false);
1124 /* Bad frame size */
1125 check_picture_size_bad_frame_size (2050, 858, 24, false);
1126 check_picture_size_bad_frame_size (2048, 658, 25, false);
1127 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1128 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1130 /* Bad 2K frame rate */
1131 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1132 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1133 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1135 /* Bad 4K frame rate */
1136 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1137 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1140 auto notes = check_picture_size(3996, 2160, 24, true);
1141 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1142 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1143 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1149 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1152 make_shared<dcp::SubtitleString>(
1160 dcp::Time(start_frame, 24, 24),
1161 dcp::Time(end_frame, 24, 24),
1177 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1179 path const dir("build/test/verify_closed_caption_xml_too_large");
1180 prepare_directory (dir);
1182 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1183 for (int i = 0; i < 2048; ++i) {
1184 add_test_subtitle (asset, i * 24, i * 24 + 20);
1186 asset->set_language (dcp::LanguageTag("de-DE"));
1187 asset->write (dir / "subs.mxf");
1188 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1189 write_dcp_with_single_asset (dir, reel_asset);
1191 check_verify_result (
1194 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1195 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES, canonical(dir / "subs.mxf") },
1196 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1197 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
1203 shared_ptr<dcp::SMPTESubtitleAsset>
1204 make_large_subtitle_asset (path font_file)
1206 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1207 dcp::ArrayData big_fake_font(1024 * 1024);
1208 big_fake_font.write (font_file);
1209 for (int i = 0; i < 116; ++i) {
1210 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1218 verify_timed_text_asset_too_large (string name)
1220 auto const dir = path("build/test") / name;
1221 prepare_directory (dir);
1222 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1223 add_test_subtitle (asset, 0, 20);
1224 asset->set_language (dcp::LanguageTag("de-DE"));
1225 asset->write (dir / "subs.mxf");
1227 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1228 write_dcp_with_single_asset (dir, reel_asset);
1230 check_verify_result (
1233 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_SIZE_IN_BYTES, canonical(dir / "subs.mxf") },
1234 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1236 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1237 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
1242 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1244 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1245 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1249 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1251 path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1252 prepare_directory (dir);
1253 auto dcp = make_simple (dir, 1, 240);
1256 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1257 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1258 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1259 "<ContentTitleText>Content</ContentTitleText>"
1260 "<AnnotationText>Annotation</AnnotationText>"
1261 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1262 "<ReelNumber>1</ReelNumber>"
1263 "<EditRate>25 1</EditRate>"
1264 "<TimeCodeRate>25</TimeCodeRate>"
1265 "<StartTime>00:00:00:00</StartTime>"
1266 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1268 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1269 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1270 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1276 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1277 BOOST_REQUIRE (xml_file);
1278 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1280 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1281 subs->write (dir / "subs.mxf");
1283 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1284 dcp->cpls().front()->reels().front()->add(reel_subs);
1287 dcp::String::compose("libdcp %1", dcp::version),
1288 dcp::String::compose("libdcp %1", dcp::version),
1289 dcp::LocalTime().as_string(),
1293 check_verify_result (
1296 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1297 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1302 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1304 path path ("build/test/verify_inconsistent_subtitle_languages");
1305 auto dcp = make_simple (path, 2, 240);
1306 auto cpl = dcp->cpls()[0];
1309 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1310 subs->set_language (dcp::LanguageTag("de-DE"));
1311 subs->add (simple_subtitle());
1312 subs->write (path / "subs1.mxf");
1313 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1314 cpl->reels()[0]->add(reel_subs);
1318 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1319 subs->set_language (dcp::LanguageTag("en-US"));
1320 subs->add (simple_subtitle());
1321 subs->write (path / "subs2.mxf");
1322 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1323 cpl->reels()[1]->add(reel_subs);
1328 dcp::String::compose("libdcp %1", dcp::version),
1329 dcp::String::compose("libdcp %1", dcp::version),
1330 dcp::LocalTime().as_string(),
1334 check_verify_result (
1337 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1338 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_SUBTITLE_LANGUAGES },
1339 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1344 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1346 path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1347 prepare_directory (dir);
1348 auto dcp = make_simple (dir, 1, 240);
1351 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1352 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1353 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1354 "<ContentTitleText>Content</ContentTitleText>"
1355 "<AnnotationText>Annotation</AnnotationText>"
1356 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1357 "<ReelNumber>1</ReelNumber>"
1358 "<Language>de-DE</Language>"
1359 "<EditRate>25 1</EditRate>"
1360 "<TimeCodeRate>25</TimeCodeRate>"
1361 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1363 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1364 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1365 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1371 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1372 BOOST_REQUIRE (xml_file);
1373 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1375 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1376 subs->write (dir / "subs.mxf");
1378 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1379 dcp->cpls().front()->reels().front()->add(reel_subs);
1382 dcp::String::compose("libdcp %1", dcp::version),
1383 dcp::String::compose("libdcp %1", dcp::version),
1384 dcp::LocalTime().as_string(),
1388 check_verify_result (
1391 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1392 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1397 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1399 path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1400 prepare_directory (dir);
1401 auto dcp = make_simple (dir, 1, 240);
1404 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1405 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1406 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1407 "<ContentTitleText>Content</ContentTitleText>"
1408 "<AnnotationText>Annotation</AnnotationText>"
1409 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1410 "<ReelNumber>1</ReelNumber>"
1411 "<Language>de-DE</Language>"
1412 "<EditRate>25 1</EditRate>"
1413 "<TimeCodeRate>25</TimeCodeRate>"
1414 "<StartTime>00:00:02:00</StartTime>"
1415 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1417 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1418 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1419 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1425 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1426 BOOST_REQUIRE (xml_file);
1427 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1429 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1430 subs->write (dir / "subs.mxf");
1432 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1433 dcp->cpls().front()->reels().front()->add(reel_subs);
1436 dcp::String::compose("libdcp %1", dcp::version),
1437 dcp::String::compose("libdcp %1", dcp::version),
1438 dcp::LocalTime().as_string(),
1442 check_verify_result (
1445 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1446 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1454 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1457 , v_position(v_position_)
1470 dcp_with_text (path dir, vector<TestText> subs)
1472 prepare_directory (dir);
1473 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1474 asset->set_start_time (dcp::Time());
1475 for (auto i: subs) {
1476 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1478 asset->set_language (dcp::LanguageTag("de-DE"));
1479 asset->write (dir / "subs.mxf");
1481 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1482 write_dcp_with_single_asset (dir, reel_asset);
1486 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1488 auto const dir = path("build/test/verify_text_too_early");
1489 /* Just too early */
1490 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1491 check_verify_result (
1494 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1495 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1501 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1503 auto const dir = path("build/test/verify_text_not_too_early");
1504 /* Just late enough */
1505 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1506 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1510 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1512 auto const dir = path("build/test/verify_text_early_on_second_reel");
1513 prepare_directory (dir);
1515 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1516 asset1->set_start_time (dcp::Time());
1517 /* Just late enough */
1518 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1519 asset1->set_language (dcp::LanguageTag("de-DE"));
1520 asset1->write (dir / "subs1.mxf");
1521 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1522 auto reel1 = make_shared<dcp::Reel>();
1523 reel1->add (reel_asset1);
1524 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1525 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1526 reel1->add (markers1);
1528 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1529 asset2->set_start_time (dcp::Time());
1530 /* This would be too early on first reel but should be OK on the second */
1531 add_test_subtitle (asset2, 0, 4 * 24);
1532 asset2->set_language (dcp::LanguageTag("de-DE"));
1533 asset2->write (dir / "subs2.mxf");
1534 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1535 auto reel2 = make_shared<dcp::Reel>();
1536 reel2->add (reel_asset2);
1537 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1538 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1539 reel2->add (markers2);
1541 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1544 auto dcp = make_shared<dcp::DCP>(dir);
1548 dcp::String::compose("libdcp %1", dcp::version),
1549 dcp::String::compose("libdcp %1", dcp::version),
1550 dcp::LocalTime().as_string(),
1555 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1559 BOOST_AUTO_TEST_CASE (verify_text_too_close)
1561 auto const dir = path("build/test/verify_text_too_close");
1562 dcp_with_text<dcp::ReelSubtitleAsset> (
1566 { 5 * 24 + 1, 6 * 24 },
1568 check_verify_result (
1571 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_SPACING },
1572 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1577 BOOST_AUTO_TEST_CASE (verify_text_not_too_close)
1579 auto const dir = path("build/test/verify_text_not_too_close");
1580 dcp_with_text<dcp::ReelSubtitleAsset> (
1584 { 5 * 24 + 16, 8 * 24 },
1586 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1590 BOOST_AUTO_TEST_CASE (verify_text_too_short)
1592 auto const dir = path("build/test/verify_text_too_short");
1593 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1594 check_verify_result (
1597 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_DURATION },
1598 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1603 BOOST_AUTO_TEST_CASE (verify_text_not_too_short)
1605 auto const dir = path("build/test/verify_text_not_too_short");
1606 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1607 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1611 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1)
1613 auto const dir = path ("build/test/verify_too_many_subtitle_lines1");
1614 dcp_with_text<dcp::ReelSubtitleAsset> (
1617 { 96, 200, 0.0, "We" },
1618 { 96, 200, 0.1, "have" },
1619 { 96, 200, 0.2, "four" },
1620 { 96, 200, 0.3, "lines" }
1622 check_verify_result (
1625 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1626 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1631 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1)
1633 auto const dir = path ("build/test/verify_not_too_many_subtitle_lines1");
1634 dcp_with_text<dcp::ReelSubtitleAsset> (
1637 { 96, 200, 0.0, "We" },
1638 { 96, 200, 0.1, "have" },
1639 { 96, 200, 0.2, "four" },
1641 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1645 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2)
1647 auto const dir = path ("build/test/verify_too_many_subtitle_lines2");
1648 dcp_with_text<dcp::ReelSubtitleAsset> (
1651 { 96, 300, 0.0, "We" },
1652 { 96, 300, 0.1, "have" },
1653 { 150, 180, 0.2, "four" },
1654 { 150, 180, 0.3, "lines" }
1656 check_verify_result (
1659 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1660 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1665 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2)
1667 auto const dir = path ("build/test/verify_not_too_many_subtitle_lines2");
1668 dcp_with_text<dcp::ReelSubtitleAsset> (
1671 { 96, 300, 0.0, "We" },
1672 { 96, 300, 0.1, "have" },
1673 { 150, 180, 0.2, "four" },
1674 { 190, 250, 0.3, "lines" }
1676 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1680 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1)
1682 auto const dir = path ("build/test/verify_subtitle_lines_too_long1");
1683 dcp_with_text<dcp::ReelSubtitleAsset> (
1686 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1688 check_verify_result (
1691 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1692 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1697 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2)
1699 auto const dir = path ("build/test/verify_subtitle_lines_too_long2");
1700 dcp_with_text<dcp::ReelSubtitleAsset> (
1703 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1705 check_verify_result (
1708 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_LENGTH },
1709 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1714 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1)
1716 auto const dir = path ("build/test/verify_too_many_closed_caption_lines1");
1717 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1720 { 96, 200, 0.0, "We" },
1721 { 96, 200, 0.1, "have" },
1722 { 96, 200, 0.2, "four" },
1723 { 96, 200, 0.3, "lines" }
1725 check_verify_result (
1728 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1729 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1734 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1)
1736 auto const dir = path ("build/test/verify_not_too_many_closed_caption_lines1");
1737 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1740 { 96, 200, 0.0, "We" },
1741 { 96, 200, 0.1, "have" },
1742 { 96, 200, 0.2, "four" },
1744 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1748 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2)
1750 auto const dir = path ("build/test/verify_too_many_closed_caption_lines2");
1751 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1754 { 96, 300, 0.0, "We" },
1755 { 96, 300, 0.1, "have" },
1756 { 150, 180, 0.2, "four" },
1757 { 150, 180, 0.3, "lines" }
1759 check_verify_result (
1762 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1763 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1768 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2)
1770 auto const dir = path ("build/test/verify_not_too_many_closed_caption_lines2");
1771 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1774 { 96, 300, 0.0, "We" },
1775 { 96, 300, 0.1, "have" },
1776 { 150, 180, 0.2, "four" },
1777 { 190, 250, 0.3, "lines" }
1779 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
1783 BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1)
1785 auto const dir = path ("build/test/verify_closed_caption_lines_too_long1");
1786 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1789 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1791 check_verify_result (
1794 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1795 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1800 BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k)
1802 path const dir("build/test/verify_sound_sampling_rate_must_be_48k");
1803 prepare_directory (dir);
1805 auto picture = simple_picture (dir, "foo");
1806 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1807 auto reel = make_shared<dcp::Reel>();
1808 reel->add (reel_picture);
1809 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1810 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1811 reel->add (reel_sound);
1812 reel->add (simple_markers());
1813 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1815 auto dcp = make_shared<dcp::DCP>(dir);
1819 dcp::String::compose("libdcp %1", dcp::version),
1820 dcp::String::compose("libdcp %1", dcp::version),
1821 dcp::LocalTime().as_string(),
1825 check_verify_result (
1828 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE, canonical(dir / "audiofoo.mxf") },
1829 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1834 BOOST_AUTO_TEST_CASE (verify_cpl_must_have_annotation_text)
1836 path const dir("build/test/verify_cpl_must_have_annotation_text");
1837 auto dcp = make_simple (dir);
1840 dcp::String::compose("libdcp %1", dcp::version),
1841 dcp::String::compose("libdcp %1", dcp::version),
1842 dcp::LocalTime().as_string(),
1846 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1849 BOOST_REQUIRE (dcp->cpls()[0]->file());
1850 Editor e(dcp->cpls()[0]->file().get());
1851 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1854 check_verify_result (
1857 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_ANNOTATION_TEXT },
1858 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES }
1863 BOOST_AUTO_TEST_CASE (verify_cpl_annotation_text_should_be_same_as_content_title_text)
1865 path const dir("build/test/verify_cpl_annotation_text_should_be_same_as_content_title_text");
1866 auto dcp = make_simple (dir);
1869 dcp::String::compose("libdcp %1", dcp::version),
1870 dcp::String::compose("libdcp %1", dcp::version),
1871 dcp::LocalTime().as_string(),
1875 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1878 BOOST_REQUIRE (dcp->cpls()[0]->file());
1879 Editor e(dcp->cpls()[0]->file().get());
1880 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1883 check_verify_result (
1886 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISMATCHED_CPL_ANNOTATION_TEXT },
1887 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES }
1892 BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match)
1894 path const dir("build/test/verify_reel_assets_durations_must_match");
1895 prepare_directory (dir);
1896 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1897 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::TRAILER));
1899 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1900 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1902 auto reel = make_shared<dcp::Reel>(
1903 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1904 make_shared<dcp::ReelSoundAsset>(ms, 0)
1907 reel->add (simple_markers());
1913 dcp::String::compose("libdcp %1", dcp::version),
1914 dcp::String::compose("libdcp %1", dcp::version),
1915 dcp::LocalTime().as_string(),
1919 check_verify_result (
1922 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION },
1923 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
1931 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1933 prepare_directory (dir);
1934 auto dcp = make_shared<dcp::DCP>(dir);
1935 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1937 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1938 subs->set_language (dcp::LanguageTag("de-DE"));
1939 subs->set_start_time (dcp::Time());
1940 subs->add (simple_subtitle());
1941 subs->write (dir / "subs.mxf");
1942 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1944 auto reel1 = make_shared<dcp::Reel>(
1945 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1946 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1950 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1953 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1954 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1955 reel1->add (markers1);
1959 auto reel2 = make_shared<dcp::Reel>(
1960 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1961 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1965 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1968 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1969 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1970 reel2->add (markers2);
1977 dcp::String::compose("libdcp %1", dcp::version),
1978 dcp::String::compose("libdcp %1", dcp::version),
1979 dcp::LocalTime().as_string(),
1985 BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels)
1988 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1989 verify_subtitles_must_be_in_all_reels_check (dir, true, false);
1990 check_verify_result (
1993 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
1994 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
2000 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2001 verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2002 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2006 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2007 verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2008 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2015 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2017 prepare_directory (dir);
2018 auto dcp = make_shared<dcp::DCP>(dir);
2019 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2021 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2022 subs->set_language (dcp::LanguageTag("de-DE"));
2023 subs->set_start_time (dcp::Time());
2024 subs->add (simple_subtitle());
2025 subs->write (dir / "subs.mxf");
2027 auto reel1 = make_shared<dcp::Reel>(
2028 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2029 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2032 for (int i = 0; i < caps_in_reel1; ++i) {
2033 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2036 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2037 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2038 reel1->add (markers1);
2042 auto reel2 = make_shared<dcp::Reel>(
2043 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2044 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2047 for (int i = 0; i < caps_in_reel2; ++i) {
2048 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2051 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2052 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2053 reel2->add (markers2);
2060 dcp::String::compose("libdcp %1", dcp::version),
2061 dcp::String::compose("libdcp %1", dcp::version),
2062 dcp::LocalTime().as_string(),
2068 BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels)
2071 path dir ("build/test/verify_closed_captions_must_be_in_all_reels1");
2072 verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2073 check_verify_result (
2076 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2077 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
2082 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2083 verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2084 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2088 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2089 verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2090 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }});
2097 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2099 prepare_directory (dir);
2100 auto dcp = make_shared<dcp::DCP>(dir);
2101 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2103 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2104 subs->set_language (dcp::LanguageTag("de-DE"));
2105 subs->set_start_time (dcp::Time());
2106 subs->add (simple_subtitle());
2107 subs->write (dir / "subs.mxf");
2108 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2111 auto reel = make_shared<dcp::Reel>(
2112 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2113 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2116 reel->add (reel_text);
2118 reel->add (simple_markers(240));
2125 dcp::String::compose("libdcp %1", dcp::version),
2126 dcp::String::compose("libdcp %1", dcp::version),
2127 dcp::LocalTime().as_string(),
2131 check_verify_result (
2134 { dcp::VerificationNote::VERIFY_BV21_ERROR, code },
2135 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA }
2140 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2142 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2143 "build/test/verify_subtitle_entry_point_must_be_present",
2144 dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT,
2145 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2146 asset->unset_entry_point ();
2150 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2151 "build/test/verify_subtitle_entry_point_must_be_zero",
2152 dcp::VerificationNote::INCORRECT_SUBTITLE_ENTRY_POINT,
2153 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2154 asset->set_entry_point (4);
2158 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2159 "build/test/verify_closed_caption_entry_point_must_be_present",
2160 dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2161 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2162 asset->unset_entry_point ();
2166 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2167 "build/test/verify_closed_caption_entry_point_must_be_zero",
2168 dcp::VerificationNote::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2169 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2170 asset->set_entry_point (9);
2176 BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes)
2180 path const dir("build/test/verify_assets_must_have_hashes");
2181 auto dcp = make_simple (dir);
2184 dcp::String::compose("libdcp %1", dcp::version),
2185 dcp::String::compose("libdcp %1", dcp::version),
2186 dcp::LocalTime().as_string(),
2190 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2193 BOOST_REQUIRE (dcp->cpls()[0]->file());
2194 Editor e(dcp->cpls()[0]->file().get());
2195 e.replace("<Hash>XGhFVrqZqapOJx5Fh2SLjj48Yjg=</Hash>", "");
2198 check_verify_result (
2201 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2202 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2209 verify_markers_test (
2211 vector<pair<dcp::Marker, dcp::Time>> markers,
2212 vector<dcp::VerificationNote> test_notes
2215 auto dcp = make_simple (dir);
2216 dcp->cpls()[0]->set_content_kind (dcp::FEATURE);
2217 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2218 for (auto const& i: markers) {
2219 markers_asset->set (i.first, i.second);
2221 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2224 dcp::String::compose("libdcp %1", dcp::version),
2225 dcp::String::compose("libdcp %1", dcp::version),
2226 dcp::LocalTime().as_string(),
2230 check_verify_result ({dir}, test_notes);
2234 BOOST_AUTO_TEST_CASE (verify_markers)
2236 verify_markers_test (
2237 "build/test/verify_markers_all_correct",
2239 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2240 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2241 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2242 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2247 verify_markers_test (
2248 "build/test/verify_markers_missing_ffec",
2250 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2251 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2252 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2255 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }
2258 verify_markers_test (
2259 "build/test/verify_markers_missing_ffmc",
2261 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2262 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2263 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2266 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }
2269 verify_markers_test (
2270 "build/test/verify_markers_missing_ffoc",
2272 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2273 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2274 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2277 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC}
2280 verify_markers_test (
2281 "build/test/verify_markers_missing_lfoc",
2283 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2284 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2285 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2288 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }
2291 verify_markers_test (
2292 "build/test/verify_markers_incorrect_ffoc",
2294 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2295 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2296 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2297 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2300 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC }
2303 verify_markers_test (
2304 "build/test/verify_markers_incorrect_lfoc",
2306 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2307 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2308 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2309 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2312 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC }
2317 BOOST_AUTO_TEST_CASE (verify_cpl_metadata_version)
2319 path dir = "build/test/verify_cpl_metadata_version";
2320 prepare_directory (dir);
2321 auto dcp = make_simple (dir);
2322 dcp->cpls()[0]->unset_version_number();
2325 dcp::String::compose("libdcp %1", dcp::version),
2326 dcp::String::compose("libdcp %1", dcp::version),
2327 dcp::LocalTime().as_string(),
2331 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER }});
2335 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata1)
2337 path dir = "build/test/verify_cpl_extension_metadata1";
2338 auto dcp = make_simple (dir);
2341 dcp::String::compose("libdcp %1", dcp::version),
2342 dcp::String::compose("libdcp %1", dcp::version),
2343 dcp::LocalTime().as_string(),
2348 Editor e (dcp->cpls()[0]->file().get());
2349 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2352 check_verify_result (
2355 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2356 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA }
2361 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata2)
2363 path dir = "build/test/verify_cpl_extension_metadata2";
2364 auto dcp = make_simple (dir);
2367 dcp::String::compose("libdcp %1", dcp::version),
2368 dcp::String::compose("libdcp %1", dcp::version),
2369 dcp::LocalTime().as_string(),
2374 Editor e (dcp->cpls()[0]->file().get());
2375 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2378 check_verify_result (
2381 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2382 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA }
2387 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata3)
2389 path dir = "build/test/verify_cpl_extension_metadata3";
2390 auto dcp = make_simple (dir);
2393 dcp::String::compose("libdcp %1", dcp::version),
2394 dcp::String::compose("libdcp %1", dcp::version),
2395 dcp::LocalTime().as_string(),
2399 path const cpl = dcp->cpls()[0]->file().get();
2403 e.replace ("<meta:Name>A", "<meta:NameX>A");
2404 e.replace ("n</meta:Name>", "n</meta:NameX>");
2407 check_verify_result (
2410 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl, 75 },
2411 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl, 82 },
2412 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2417 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata4)
2419 path dir = "build/test/verify_cpl_extension_metadata4";
2420 auto dcp = make_simple (dir);
2423 dcp::String::compose("libdcp %1", dcp::version),
2424 dcp::String::compose("libdcp %1", dcp::version),
2425 dcp::LocalTime().as_string(),
2430 Editor e (dcp->cpls()[0]->file().get());
2431 e.replace ("Application", "Fred");
2434 check_verify_result (
2437 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2438 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'") },
2443 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata5)
2445 path dir = "build/test/verify_cpl_extension_metadata5";
2446 auto dcp = make_simple (dir);
2449 dcp::String::compose("libdcp %1", dcp::version),
2450 dcp::String::compose("libdcp %1", dcp::version),
2451 dcp::LocalTime().as_string(),
2455 Editor e (dcp->cpls()[0]->file().get());
2456 e.replace ("DCP Constraints Profile", "Fred");
2459 check_verify_result (
2462 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2463 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'") },
2468 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata6)
2470 path dir = "build/test/verify_cpl_extension_metadata6";
2471 auto dcp = make_simple (dir);
2474 dcp::String::compose("libdcp %1", dcp::version),
2475 dcp::String::compose("libdcp %1", dcp::version),
2476 dcp::LocalTime().as_string(),
2480 path const cpl = dcp->cpls()[0]->file().get();
2484 e.replace ("<meta:Value>", "<meta:ValueX>");
2485 e.replace ("</meta:Value>", "</meta:ValueX>");
2488 check_verify_result (
2491 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl, 79 },
2492 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl, 80 },
2493 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2498 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata7)
2500 path dir = "build/test/verify_cpl_extension_metadata7";
2501 auto dcp = make_simple (dir);
2504 dcp::String::compose("libdcp %1", dcp::version),
2505 dcp::String::compose("libdcp %1", dcp::version),
2506 dcp::LocalTime().as_string(),
2510 Editor e (dcp->cpls()[0]->file().get());
2511 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2514 check_verify_result (
2517 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2518 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'") },
2523 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata8)
2525 path dir = "build/test/verify_cpl_extension_metadata8";
2526 auto dcp = make_simple (dir);
2529 dcp::String::compose("libdcp %1", dcp::version),
2530 dcp::String::compose("libdcp %1", dcp::version),
2531 dcp::LocalTime().as_string(),
2535 path const cpl = dcp->cpls()[0]->file().get();
2539 e.replace ("<meta:Property>", "<meta:PropertyX>");
2540 e.replace ("</meta:Property>", "</meta:PropertyX>");
2543 check_verify_result (
2546 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl, 77 },
2547 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl, 81 },
2548 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2553 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata9)
2555 path dir = "build/test/verify_cpl_extension_metadata9";
2556 auto dcp = make_simple (dir);
2559 dcp::String::compose("libdcp %1", dcp::version),
2560 dcp::String::compose("libdcp %1", dcp::version),
2561 dcp::LocalTime().as_string(),
2565 path const cpl = dcp->cpls()[0]->file().get();
2569 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2570 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2573 check_verify_result (
2576 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl, 76 },
2577 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl, 82 },
2578 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2584 BOOST_AUTO_TEST_CASE (verify_encrypted_cpl_is_signed)
2586 path dir = "build/test/verify_encrypted_cpl_is_signed";
2587 prepare_directory (dir);
2588 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2589 copy_file (i.path(), dir / i.path().filename());
2592 path const pkl = dir / "pkl_93182bd2-b1e8-41a3-b5c8-6e6564273bff.xml";
2593 path const cpl = dir / "cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
2597 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2600 check_verify_result (
2603 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES },
2604 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, canonical(pkl), },
2605 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2606 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2607 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2608 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2609 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
2610 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, canonical(cpl) }
2615 BOOST_AUTO_TEST_CASE (verify_encrypted_pkl_is_signed)
2617 path dir = "build/test/verify_encrypted_pkl_is_signed";
2618 prepare_directory (dir);
2619 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2620 copy_file (i.path(), dir / i.path().filename());
2623 path const pkl = dir / "pkl_93182bd2-b1e8-41a3-b5c8-6e6564273bff.xml";
2626 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2629 check_verify_result (
2632 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, canonical(pkl) },
2633 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2634 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2635 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2636 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2637 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA },
2638 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, canonical(pkl) },
2643 BOOST_AUTO_TEST_CASE (verify_unencrypted_pkl_can_be_unsigned)
2645 path dir = "build/test/verify_unencrypted_pkl_can_be_unsigned";
2646 prepare_directory (dir);
2647 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2648 copy_file (i.path(), dir / i.path().filename());
2652 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2653 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2656 check_verify_result ({dir}, {});
2660 BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted)
2662 path dir ("build/test/verify_must_not_be_partially_encrypted");
2663 prepare_directory (dir);
2667 auto signer = make_shared<dcp::CertificateChain>();
2668 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2669 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2670 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2671 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2673 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2677 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::SMPTE);
2680 auto writer = mp->start_write (dir / "video.mxf", false);
2681 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2682 for (int i = 0; i < 24; ++i) {
2683 writer->write (j2c.data(), j2c.size());
2685 writer->finalize ();
2687 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2689 auto reel = make_shared<dcp::Reel>(
2690 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2691 make_shared<dcp::ReelSoundAsset>(ms, 0)
2694 reel->add (simple_markers());
2698 cpl->set_content_version (
2699 {"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"}
2701 cpl->set_annotation_text ("A Test DCP");
2702 cpl->set_issuer ("OpenDCP 0.0.25");
2703 cpl->set_creator ("OpenDCP 0.0.25");
2704 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2705 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2706 cpl->set_main_sound_sample_rate (48000);
2707 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2708 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2709 cpl->set_version_number (1);
2713 d.write_xml (dcp::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2715 check_verify_result ({dir}, {{dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PARTIALLY_ENCRYPTED}});