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.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
70 using std::make_shared;
72 using std::shared_ptr;
75 using boost::optional;
76 using namespace boost::filesystem;
79 static list<pair<string, optional<path>>> stages;
81 static string filename_to_id(boost::filesystem::path path)
83 return path.string().substr(4, path.string().length() - 8);
87 boost::filesystem::path
90 return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
97 return filename_to_id(dcp_test1_pkl());
101 boost::filesystem::path
104 return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
111 return filename_to_id(dcp_test1_cpl());
114 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
118 encryption_test_cpl_id()
120 return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
125 encryption_test_pkl_id()
127 return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
131 stage (string s, optional<path> p)
133 stages.push_back (make_pair (s, p));
143 prepare_directory (path path)
145 using namespace boost::filesystem;
147 create_directories (path);
151 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
152 * to make a new sacrificial test DCP.
155 setup (int reference_number, string verify_test_suffix)
157 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
158 prepare_directory (dir);
159 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
160 copy_file (i.path(), dir / i.path().filename());
169 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
171 auto reel = make_shared<dcp::Reel>();
172 reel->add (reel_asset);
173 reel->add (simple_markers());
175 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
177 auto dcp = make_shared<dcp::DCP>(dir);
179 dcp->set_annotation_text("hello");
186 LIBDCP_DISABLE_WARNINGS
189 dump_notes (vector<dcp::VerificationNote> const & notes)
191 for (auto i: notes) {
192 std::cout << dcp::note_to_string(i) << "\n";
195 LIBDCP_ENABLE_WARNINGS
200 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
202 auto notes = dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test);
203 std::sort (notes.begin(), notes.end());
204 std::sort (test_notes.begin(), test_notes.end());
206 string message = "\nVerification notes from test:\n";
207 for (auto i: notes) {
208 message += " " + note_to_string(i) + "\n";
209 message += dcp::String::compose(
210 " [%1 %2 %3 %4 %5 %6 %7]\n",
211 static_cast<int>(i.type()),
212 static_cast<int>(i.code()),
213 i.note().get_value_or("<none>"),
214 i.file().get_value_or("<none>"),
215 i.line().get_value_or(0),
216 i.reference_hash().get_value_or("<none>"),
217 i.calculated_hash().get_value_or("<none>")
220 message += "Expected:\n";
221 for (auto i: test_notes) {
222 message += " " + note_to_string(i) + "\n";
223 message += dcp::String::compose(
224 " [%1 %2 %3 %4 %5 %6 %7]\n",
225 static_cast<int>(i.type()),
226 static_cast<int>(i.code()),
227 i.note().get_value_or("<none>"),
228 i.file().get_value_or("<none>"),
229 i.line().get_value_or(0),
230 i.reference_hash().get_value_or("<none>"),
231 i.calculated_hash().get_value_or("<none>")
235 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
239 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
240 * replacing from with to. Verify the resulting DCP and check that the results match the given
245 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
247 auto dir = setup (1, suffix);
250 Editor e (file(suffix));
251 e.replace (from, to);
254 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
256 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
257 auto i = notes.begin();
258 auto j = codes.begin();
259 while (i != notes.end()) {
260 BOOST_CHECK_EQUAL (i->code(), *j);
269 add_font(shared_ptr<dcp::SubtitleAsset> asset)
271 dcp::ArrayData fake_font(1024);
272 asset->add_font("font", fake_font);
279 HashCalculator(boost::filesystem::path path)
281 , _old_hash(dcp::make_digest(path, [](int64_t, int64_t) {}))
284 std::string old_hash() const {
288 std::string new_hash() const {
289 return dcp::make_digest(_path, [](int64_t, int64_t) {});
293 boost::filesystem::path _path;
294 std::string _old_hash;
298 BOOST_AUTO_TEST_CASE (verify_no_error)
301 auto dir = setup (1, "no_error");
302 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
304 path const cpl_file = dir / dcp_test1_cpl();
305 path const pkl_file = dir / dcp_test1_pkl();
306 path const assetmap_file = dir / "ASSETMAP.xml";
308 auto st = stages.begin();
309 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
310 BOOST_REQUIRE (st->second);
311 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
313 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
314 BOOST_REQUIRE (st->second);
315 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
317 BOOST_CHECK_EQUAL (st->first, "Checking reel");
318 BOOST_REQUIRE (!st->second);
320 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
321 BOOST_REQUIRE (st->second);
322 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
324 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
325 BOOST_REQUIRE (st->second);
326 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
328 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
329 BOOST_REQUIRE (st->second);
330 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
332 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
333 BOOST_REQUIRE (st->second);
334 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
336 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
337 BOOST_REQUIRE (st->second);
338 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
339 ++st; BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
340 BOOST_REQUIRE (st->second);
341 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
343 BOOST_REQUIRE (st == stages.end());
345 BOOST_CHECK_EQUAL (notes.size(), 0U);
349 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
351 using namespace boost::filesystem;
353 auto dir = setup (1, "incorrect_picture_sound_hash");
355 auto video_path = path(dir / "video.mxf");
356 HashCalculator video_calc(video_path);
357 auto mod = fopen(video_path.string().c_str(), "r+b");
359 fseek (mod, 4096, SEEK_SET);
361 fwrite (&x, sizeof(x), 1, mod);
364 auto audio_path = path(dir / "audio.mxf");
365 HashCalculator audio_calc(audio_path);
366 mod = fopen(audio_path.string().c_str(), "r+b");
368 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
369 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
372 dcp::ASDCPErrorSuspender sus;
373 check_verify_result (
377 dcp::VerificationNote(
378 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path)
379 ).set_reference_hash(video_calc.old_hash()).set_calculated_hash(video_calc.new_hash()),
380 dcp::VerificationNote(
381 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path)
382 ).set_reference_hash(audio_calc.old_hash()).set_calculated_hash(audio_calc.new_hash()),
387 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
389 using namespace boost::filesystem;
391 auto dir = setup (1, "mismatched_picture_sound_hashes");
393 HashCalculator calc(dir / dcp_test1_cpl());
396 Editor e (dir / dcp_test1_pkl());
397 e.replace ("<Hash>", "<Hash>x");
400 check_verify_result (
404 dcp::VerificationNote(
405 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl())
406 ).set_reference_hash("x" + calc.old_hash()).set_calculated_hash(calc.old_hash()),
407 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
408 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
409 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
410 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 12 },
411 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
416 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
418 auto dir = setup (1, "failed_read_content_kind");
420 HashCalculator calc(dir / dcp_test1_cpl());
423 Editor e (dir / dcp_test1_cpl());
424 e.replace ("<ContentKind>", "<ContentKind>x");
427 check_verify_result (
431 dcp::VerificationNote(
432 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl())
433 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
434 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
443 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
451 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
457 asset_map (string suffix)
459 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
463 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
465 check_verify_result_after_replace (
466 "invalid_picture_frame_rate", &cpl,
467 "<FrameRate>24 1", "<FrameRate>99 1",
468 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
469 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
473 BOOST_AUTO_TEST_CASE (verify_missing_asset)
475 auto dir = setup (1, "missing_asset");
476 remove (dir / "video.mxf");
477 check_verify_result (
481 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
486 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
488 check_verify_result_after_replace (
489 "empty_asset_path", &asset_map,
490 "<Path>video.mxf</Path>", "<Path></Path>",
491 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
496 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
498 check_verify_result_after_replace (
499 "mismatched_standard", &cpl,
500 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
501 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
502 dcp::VerificationNote::Code::INVALID_XML,
503 dcp::VerificationNote::Code::INVALID_XML,
504 dcp::VerificationNote::Code::INVALID_XML,
505 dcp::VerificationNote::Code::INVALID_XML,
506 dcp::VerificationNote::Code::INVALID_XML,
507 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
512 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
514 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
515 check_verify_result_after_replace (
516 "invalid_xml_cpl_id", &cpl,
517 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
518 { dcp::VerificationNote::Code::INVALID_XML }
523 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
525 check_verify_result_after_replace (
526 "invalid_xml_issue_date", &cpl,
527 "<IssueDate>", "<IssueDate>x",
528 { dcp::VerificationNote::Code::INVALID_XML,
529 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
534 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
536 check_verify_result_after_replace (
537 "invalid_xml_pkl_id", &pkl,
538 "<Id>urn:uuid:" + dcp_test1_pkl_id().substr(0, 3),
539 "<Id>urn:uuid:x" + dcp_test1_pkl_id().substr(1, 2),
540 { dcp::VerificationNote::Code::INVALID_XML }
545 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
547 check_verify_result_after_replace (
548 "invalid_xml_asset_map_id", &asset_map,
549 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
550 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
551 { dcp::VerificationNote::Code::INVALID_XML }
556 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
559 auto dir = setup (3, "verify_invalid_standard");
560 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
562 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
563 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
564 path const assetmap_file = dir / "ASSETMAP";
566 auto st = stages.begin();
567 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
568 BOOST_REQUIRE (st->second);
569 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
571 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
572 BOOST_REQUIRE (st->second);
573 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
575 BOOST_CHECK_EQUAL (st->first, "Checking reel");
576 BOOST_REQUIRE (!st->second);
578 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
579 BOOST_REQUIRE (st->second);
580 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
582 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
583 BOOST_REQUIRE (st->second);
584 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
586 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
587 BOOST_REQUIRE (st->second);
588 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
590 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
591 BOOST_REQUIRE (st->second);
592 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
594 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
595 BOOST_REQUIRE (st->second);
596 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
598 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
599 BOOST_REQUIRE (st->second);
600 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
602 BOOST_REQUIRE (st == stages.end());
604 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
605 auto i = notes.begin ();
606 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
607 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
609 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
610 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
613 /* DCP with a short asset */
614 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
616 auto dir = setup (8, "invalid_duration");
620 BOOST_REQUIRE(dcp.cpls().size() == 1);
621 auto cpl = dcp.cpls()[0];
623 check_verify_result (
627 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
628 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
629 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
630 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
631 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
632 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") },
633 dcp::VerificationNote(
634 dcp::VerificationNote::Type::WARNING,
635 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
637 ).set_id("d74fda30-d5f4-4c5f-870f-ebc089d97eb7")
644 dcp_from_frame (dcp::ArrayData const& frame, path dir)
646 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
647 create_directories (dir);
648 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
649 for (int i = 0; i < 24; ++i) {
650 writer->write (frame.data(), frame.size());
654 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
655 return write_dcp_with_single_asset (dir, reel_asset);
659 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
661 int const too_big = 1302083 * 2;
663 /* Compress a black image */
664 auto image = black_image ();
665 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
666 BOOST_REQUIRE (frame.size() < too_big);
668 /* Place it in a bigger block with some zero padding at the end */
669 dcp::ArrayData oversized_frame(too_big);
670 memcpy (oversized_frame.data(), frame.data(), frame.size());
671 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
673 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
674 prepare_directory (dir);
675 auto cpl = dcp_from_frame (oversized_frame, dir);
677 vector<dcp::VerificationNote> expected;
678 for (auto i = 0; i < 24; ++i) {
680 dcp::VerificationNote(
681 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
682 ).set_frame(i).set_frame_rate(24)
686 for (auto i = 0; i < 24; ++i) {
688 dcp::VerificationNote(
689 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
690 ).set_frame(i).set_frame_rate(24)
695 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
698 check_verify_result({ dir }, {}, expected);
702 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
704 int const nearly_too_big = 1302083 * 0.98;
706 /* Compress a black image */
707 auto image = black_image ();
708 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
709 BOOST_REQUIRE (frame.size() < nearly_too_big);
711 /* Place it in a bigger block with some zero padding at the end */
712 dcp::ArrayData oversized_frame(nearly_too_big);
713 memcpy (oversized_frame.data(), frame.data(), frame.size());
714 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
716 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
717 prepare_directory (dir);
718 auto cpl = dcp_from_frame (oversized_frame, dir);
720 vector<dcp::VerificationNote> expected;
722 for (auto i = 0; i < 24; ++i) {
724 dcp::VerificationNote(
725 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
726 ).set_frame(i).set_frame_rate(24)
730 for (auto i = 0; i < 24; ++i) {
732 dcp::VerificationNote(
733 dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf")
734 ).set_frame(i).set_frame_rate(24)
739 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
742 check_verify_result ({ dir }, {}, expected);
746 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
748 /* Compress a black image */
749 auto image = black_image ();
750 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
751 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
753 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
754 prepare_directory (dir);
755 auto cpl = dcp_from_frame (frame, dir);
757 check_verify_result({ dir }, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
761 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
763 path const dir("build/test/verify_valid_interop_subtitles");
764 prepare_directory (dir);
765 copy_file ("test/data/subs1.xml", dir / "subs.xml");
766 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
767 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
768 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
770 check_verify_result (
774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
775 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
780 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
782 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
783 prepare_directory(dir);
784 copy_file("test/data/subs1.xml", dir / "ccap.xml");
785 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
786 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
787 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
789 check_verify_result (
793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
794 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
799 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
801 using namespace boost::filesystem;
803 path const dir("build/test/verify_invalid_interop_subtitles");
804 prepare_directory (dir);
805 copy_file ("test/data/subs1.xml", dir / "subs.xml");
806 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
807 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
808 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
811 Editor e (dir / "subs.xml");
812 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
815 check_verify_result (
819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
822 dcp::VerificationNote::Type::ERROR,
823 dcp::VerificationNote::Code::INVALID_XML,
824 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
828 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
833 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
835 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
836 prepare_directory(dir);
837 copy_file("test/data/subs4.xml", dir / "subs.xml");
838 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
839 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
840 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
842 check_verify_result (
846 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
847 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
854 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
856 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
857 prepare_directory(dir);
858 copy_file("test/data/subs5.xml", dir / "subs.xml");
859 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
860 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
861 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
863 check_verify_result (
867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
868 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
874 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
876 path const dir("build/test/verify_valid_smpte_subtitles");
877 prepare_directory (dir);
878 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
879 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
880 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
881 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
888 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
889 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
894 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
896 using namespace boost::filesystem;
898 path const dir("build/test/verify_invalid_smpte_subtitles");
899 prepare_directory (dir);
900 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
901 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
902 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
903 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
904 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
906 check_verify_result (
910 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
912 dcp::VerificationNote::Type::ERROR,
913 dcp::VerificationNote::Code::INVALID_XML,
914 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
920 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
921 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
926 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
928 path const dir("build/test/verify_empty_text_node_in_subtitles");
929 prepare_directory (dir);
930 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
931 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
932 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
933 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
935 check_verify_result (
939 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
940 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
941 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
942 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
943 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
944 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
949 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
950 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
952 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
953 prepare_directory (dir);
954 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
955 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
956 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
957 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
959 check_verify_result (
963 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
964 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
969 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
970 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
972 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
973 prepare_directory (dir);
974 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
975 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
976 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
977 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
979 check_verify_result (
983 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
985 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
986 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
991 BOOST_AUTO_TEST_CASE (verify_external_asset)
993 path const ov_dir("build/test/verify_external_asset");
994 prepare_directory (ov_dir);
996 auto image = black_image ();
997 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
998 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
999 dcp_from_frame (frame, ov_dir);
1001 dcp::DCP ov (ov_dir);
1004 path const vf_dir("build/test/verify_external_asset_vf");
1005 prepare_directory (vf_dir);
1007 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
1008 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
1010 check_verify_result (
1014 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
1015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1020 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1022 path const dir("build/test/verify_valid_cpl_metadata");
1023 prepare_directory (dir);
1025 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1026 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1027 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1029 auto reel = make_shared<dcp::Reel>();
1030 reel->add (reel_asset);
1032 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1033 reel->add (simple_markers(16 * 24));
1035 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1037 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1038 cpl->set_main_sound_sample_rate (48000);
1039 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1040 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1041 cpl->set_version_number (1);
1045 dcp.set_annotation_text("hello");
1051 find_prefix(path dir, string prefix)
1053 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1054 return boost::starts_with(p.filename().string(), prefix);
1057 BOOST_REQUIRE(iter != directory_iterator());
1058 return iter->path();
1062 path find_cpl (path dir)
1064 return find_prefix(dir, "cpl_");
1071 return find_prefix(dir, "pkl_");
1076 find_asset_map(path dir)
1078 return find_prefix(dir, "ASSETMAP");
1082 /* DCP with invalid CompositionMetadataAsset */
1083 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1085 using namespace boost::filesystem;
1087 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1088 prepare_directory (dir);
1090 auto reel = make_shared<dcp::Reel>();
1091 reel->add (black_picture_asset(dir));
1092 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1094 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1095 cpl->set_main_sound_sample_rate (48000);
1096 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1097 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1098 cpl->set_version_number (1);
1100 reel->add (simple_markers());
1104 dcp.set_annotation_text("hello");
1107 HashCalculator calc(find_cpl(dir));
1110 Editor e (find_cpl(dir));
1111 e.replace ("MainSound", "MainSoundX");
1114 check_verify_result (
1118 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1119 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1121 dcp::VerificationNote::Type::ERROR,
1122 dcp::VerificationNote::Code::INVALID_XML,
1123 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1124 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1125 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1126 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1127 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1128 "ExtensionMetadataList?,)'"),
1129 canonical(cpl->file().get()),
1132 dcp::VerificationNote(
1133 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get())
1134 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
1139 /* DCP with invalid CompositionMetadataAsset */
1140 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1142 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1143 prepare_directory (dir);
1145 auto reel = make_shared<dcp::Reel>();
1146 reel->add (black_picture_asset(dir));
1147 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1149 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1150 cpl->set_main_sound_sample_rate (48000);
1151 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1152 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1156 dcp.set_annotation_text("hello");
1160 Editor e (find_cpl(dir));
1161 e.replace ("meta:Width", "meta:WidthX");
1164 check_verify_result (
1167 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1172 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1174 path const dir("build/test/verify_invalid_language1");
1175 prepare_directory (dir);
1176 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1177 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1178 asset->_language = "wrong-andbad";
1179 asset->write (dir / "subs.mxf");
1180 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1181 reel_asset->_language = "badlang";
1182 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1184 check_verify_result (
1188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1195 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1196 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1198 path const dir("build/test/verify_invalid_language2");
1199 prepare_directory (dir);
1200 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1201 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1202 asset->_language = "wrong-andbad";
1203 asset->write (dir / "subs.mxf");
1204 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1205 reel_asset->_language = "badlang";
1206 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1208 check_verify_result (
1212 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1213 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1214 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1219 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1220 * the release territory.
1222 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1224 path const dir("build/test/verify_invalid_language3");
1225 prepare_directory (dir);
1227 auto picture = simple_picture (dir, "foo");
1228 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1229 auto reel = make_shared<dcp::Reel>();
1230 reel->add (reel_picture);
1231 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1232 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1233 reel->add (reel_sound);
1234 reel->add (simple_markers());
1236 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1238 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1239 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1240 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1241 cpl->set_main_sound_sample_rate (48000);
1242 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1243 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1244 cpl->set_version_number (1);
1245 cpl->_release_territory = "fred-jim";
1246 auto dcp = make_shared<dcp::DCP>(dir);
1248 dcp->set_annotation_text("hello");
1251 check_verify_result (
1255 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1256 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1257 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1258 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1264 vector<dcp::VerificationNote>
1265 check_picture_size (int width, int height, int frame_rate, bool three_d)
1267 using namespace boost::filesystem;
1269 path dcp_path = "build/test/verify_picture_test";
1270 prepare_directory (dcp_path);
1272 shared_ptr<dcp::PictureAsset> mp;
1274 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1276 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1278 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1280 auto image = black_image (dcp::Size(width, height));
1281 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1282 int const length = three_d ? frame_rate * 2 : frame_rate;
1283 for (int i = 0; i < length; ++i) {
1284 picture_writer->write (j2c.data(), j2c.size());
1286 picture_writer->finalize ();
1288 auto d = make_shared<dcp::DCP>(dcp_path);
1289 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1290 cpl->set_annotation_text ("A Test DCP");
1291 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1292 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1293 cpl->set_main_sound_sample_rate (48000);
1294 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1295 cpl->set_main_picture_active_area(dcp::Size(width, height));
1296 cpl->set_version_number (1);
1298 auto reel = make_shared<dcp::Reel>();
1301 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1303 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1306 reel->add (simple_markers(frame_rate));
1311 d->set_annotation_text("A Test DCP");
1314 return dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test);
1320 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1322 auto notes = check_picture_size(width, height, frame_rate, three_d);
1323 BOOST_CHECK_EQUAL (notes.size(), 0U);
1329 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1331 auto notes = check_picture_size(width, height, frame_rate, three_d);
1332 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1333 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1334 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1340 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1342 auto notes = check_picture_size(width, height, frame_rate, three_d);
1343 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1344 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1345 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1351 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1353 auto notes = check_picture_size(width, height, frame_rate, three_d);
1354 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1355 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1356 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1360 BOOST_AUTO_TEST_CASE (verify_picture_size)
1362 using namespace boost::filesystem;
1365 check_picture_size_ok (2048, 858, 24, false);
1366 check_picture_size_ok (2048, 858, 25, false);
1367 check_picture_size_ok (2048, 858, 48, false);
1368 check_picture_size_ok (2048, 858, 24, true);
1369 check_picture_size_ok (2048, 858, 25, true);
1370 check_picture_size_ok (2048, 858, 48, true);
1373 check_picture_size_ok (1998, 1080, 24, false);
1374 check_picture_size_ok (1998, 1080, 25, false);
1375 check_picture_size_ok (1998, 1080, 48, false);
1376 check_picture_size_ok (1998, 1080, 24, true);
1377 check_picture_size_ok (1998, 1080, 25, true);
1378 check_picture_size_ok (1998, 1080, 48, true);
1381 check_picture_size_ok (4096, 1716, 24, false);
1384 check_picture_size_ok (3996, 2160, 24, false);
1386 /* Bad frame size */
1387 check_picture_size_bad_frame_size (2050, 858, 24, false);
1388 check_picture_size_bad_frame_size (2048, 658, 25, false);
1389 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1390 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1392 /* Bad 2K frame rate */
1393 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1394 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1395 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1397 /* Bad 4K frame rate */
1398 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1399 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1402 auto notes = check_picture_size(3996, 2160, 24, true);
1403 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1404 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1405 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1411 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello")
1414 std::make_shared<dcp::SubtitleString>(
1422 dcp::Time(start_frame, 24, 24),
1423 dcp::Time(end_frame, 24, 24),
1425 dcp::HAlign::CENTER,
1429 dcp::Direction::LTR,
1436 std::vector<dcp::Ruby>()
1442 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1444 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1445 prepare_directory (dir);
1447 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1448 for (int i = 0; i < 2048; ++i) {
1449 add_test_subtitle (asset, i * 24, i * 24 + 20);
1452 asset->set_language (dcp::LanguageTag("de-DE"));
1453 asset->write (dir / "subs.mxf");
1454 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1455 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1457 check_verify_result (
1461 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1463 dcp::VerificationNote::Type::BV21_ERROR,
1464 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1466 canonical(dir / "subs.mxf")
1468 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1475 shared_ptr<dcp::SMPTESubtitleAsset>
1476 make_large_subtitle_asset (path font_file)
1478 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1479 dcp::ArrayData big_fake_font(1024 * 1024);
1480 big_fake_font.write (font_file);
1481 for (int i = 0; i < 116; ++i) {
1482 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1490 verify_timed_text_asset_too_large (string name)
1492 auto const dir = path("build/test") / name;
1493 prepare_directory (dir);
1494 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1495 add_test_subtitle (asset, 0, 240);
1496 asset->set_language (dcp::LanguageTag("de-DE"));
1497 asset->write (dir / "subs.mxf");
1499 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1500 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1502 check_verify_result (
1506 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1507 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1508 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1509 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1510 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1515 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1517 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1518 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1522 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1524 path dir = "build/test/verify_missing_subtitle_language";
1525 prepare_directory (dir);
1526 auto dcp = make_simple (dir, 1, 106);
1529 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1530 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1531 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1532 "<ContentTitleText>Content</ContentTitleText>"
1533 "<AnnotationText>Annotation</AnnotationText>"
1534 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1535 "<ReelNumber>1</ReelNumber>"
1536 "<EditRate>24 1</EditRate>"
1537 "<TimeCodeRate>24</TimeCodeRate>"
1538 "<StartTime>00:00:00:00</StartTime>"
1539 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1541 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1542 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1543 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1549 dcp::File xml_file(dir / "subs.xml", "w");
1550 BOOST_REQUIRE (xml_file);
1551 xml_file.write(xml.c_str(), xml.size(), 1);
1553 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1554 subs->write (dir / "subs.mxf");
1556 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1557 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1560 check_verify_result (
1564 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1565 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1570 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1572 path path ("build/test/verify_mismatched_subtitle_languages");
1573 auto constexpr reel_length = 192;
1574 auto dcp = make_simple (path, 2, reel_length);
1575 auto cpl = dcp->cpls()[0];
1578 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1579 subs->set_language (dcp::LanguageTag("de-DE"));
1580 subs->add (simple_subtitle());
1582 subs->write (path / "subs1.mxf");
1583 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1584 cpl->reels()[0]->add(reel_subs);
1588 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1589 subs->set_language (dcp::LanguageTag("en-US"));
1590 subs->add (simple_subtitle());
1592 subs->write (path / "subs2.mxf");
1593 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1594 cpl->reels()[1]->add(reel_subs);
1599 check_verify_result (
1603 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1605 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1610 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1612 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1613 auto constexpr reel_length = 192;
1614 auto dcp = make_simple (path, 2, reel_length);
1615 auto cpl = dcp->cpls()[0];
1618 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1619 ccaps->set_language (dcp::LanguageTag("de-DE"));
1620 ccaps->add (simple_subtitle());
1622 ccaps->write (path / "subs1.mxf");
1623 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1624 cpl->reels()[0]->add(reel_ccaps);
1628 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1629 ccaps->set_language (dcp::LanguageTag("en-US"));
1630 ccaps->add (simple_subtitle());
1632 ccaps->write (path / "subs2.mxf");
1633 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1634 cpl->reels()[1]->add(reel_ccaps);
1639 check_verify_result (
1643 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1644 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1649 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1651 path dir = "build/test/verify_missing_subtitle_start_time";
1652 prepare_directory (dir);
1653 auto dcp = make_simple (dir, 1, 106);
1656 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1657 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1658 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1659 "<ContentTitleText>Content</ContentTitleText>"
1660 "<AnnotationText>Annotation</AnnotationText>"
1661 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1662 "<ReelNumber>1</ReelNumber>"
1663 "<Language>de-DE</Language>"
1664 "<EditRate>24 1</EditRate>"
1665 "<TimeCodeRate>24</TimeCodeRate>"
1666 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1668 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1669 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1670 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1676 dcp::File xml_file(dir / "subs.xml", "w");
1677 BOOST_REQUIRE (xml_file);
1678 xml_file.write(xml.c_str(), xml.size(), 1);
1680 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1681 subs->write (dir / "subs.mxf");
1683 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1684 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1687 check_verify_result (
1691 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1692 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1697 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1699 path dir = "build/test/verify_invalid_subtitle_start_time";
1700 prepare_directory (dir);
1701 auto dcp = make_simple (dir, 1, 106);
1704 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1705 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1706 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1707 "<ContentTitleText>Content</ContentTitleText>"
1708 "<AnnotationText>Annotation</AnnotationText>"
1709 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1710 "<ReelNumber>1</ReelNumber>"
1711 "<Language>de-DE</Language>"
1712 "<EditRate>24 1</EditRate>"
1713 "<TimeCodeRate>24</TimeCodeRate>"
1714 "<StartTime>00:00:02:00</StartTime>"
1715 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1717 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1718 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1719 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1725 dcp::File xml_file(dir / "subs.xml", "w");
1726 BOOST_REQUIRE (xml_file);
1727 xml_file.write(xml.c_str(), xml.size(), 1);
1729 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1730 subs->write (dir / "subs.mxf");
1732 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1733 dcp->cpls().front()->reels().front()->add(reel_subs);
1736 check_verify_result (
1740 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1741 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1749 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1752 , v_position(v_position_)
1760 dcp::VAlign v_align;
1766 shared_ptr<dcp::CPL>
1767 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
1769 prepare_directory (dir);
1770 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1771 asset->set_start_time (dcp::Time());
1772 for (auto i: subs) {
1773 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1775 asset->set_language (dcp::LanguageTag("de-DE"));
1776 if (key && key_id) {
1777 asset->set_key(*key);
1778 asset->set_key_id(*key_id);
1781 asset->write (dir / "subs.mxf");
1783 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1784 return write_dcp_with_single_asset (dir, reel_asset);
1789 shared_ptr<dcp::CPL>
1790 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1792 prepare_directory (dir);
1793 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1794 asset->set_start_time (dcp::Time());
1795 asset->set_language (dcp::LanguageTag("de-DE"));
1797 auto subs_mxf = dir / "subs.mxf";
1798 asset->write (subs_mxf);
1800 /* The call to write() puts the asset into the DCP correctly but it will have
1801 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1804 ASDCP::TimedText::MXFWriter writer;
1805 ASDCP::WriterInfo writer_info;
1806 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1808 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1809 DCP_ASSERT (c == Kumu::UUID_Length);
1810 ASDCP::TimedText::TimedTextDescriptor descriptor;
1811 descriptor.ContainerDuration = asset->intrinsic_duration();
1812 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1813 DCP_ASSERT (c == Kumu::UUID_Length);
1814 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1815 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1816 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1817 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1820 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1821 return write_dcp_with_single_asset (dir, reel_asset);
1825 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1827 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1828 /* Just too early */
1829 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1830 check_verify_result (
1834 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1841 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1843 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1844 /* Just late enough */
1845 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1846 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1850 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1852 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1853 prepare_directory (dir);
1855 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1856 asset1->set_start_time (dcp::Time());
1857 /* Just late enough */
1858 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1859 asset1->set_language (dcp::LanguageTag("de-DE"));
1861 asset1->write (dir / "subs1.mxf");
1862 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1863 auto reel1 = make_shared<dcp::Reel>();
1864 reel1->add (reel_asset1);
1865 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1866 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1867 reel1->add (markers1);
1869 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1870 asset2->set_start_time (dcp::Time());
1872 /* This would be too early on first reel but should be OK on the second */
1873 add_test_subtitle (asset2, 3, 4 * 24);
1874 asset2->set_language (dcp::LanguageTag("de-DE"));
1875 asset2->write (dir / "subs2.mxf");
1876 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1877 auto reel2 = make_shared<dcp::Reel>();
1878 reel2->add (reel_asset2);
1879 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1880 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1881 reel2->add (markers2);
1883 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1886 auto dcp = make_shared<dcp::DCP>(dir);
1888 dcp->set_annotation_text("hello");
1891 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1895 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1897 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1898 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1902 { 5 * 24 + 1, 6 * 24 },
1904 check_verify_result (
1908 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1909 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1914 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1916 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1917 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1921 { 5 * 24 + 16, 8 * 24 },
1923 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1927 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1929 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1930 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1931 check_verify_result (
1935 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1941 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1943 auto const dir = path("build/test/verify_valid_subtitle_duration");
1944 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1945 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1949 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1951 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1952 prepare_directory (dir);
1953 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1954 asset->set_start_time (dcp::Time());
1955 add_test_subtitle (asset, 0, 4 * 24);
1957 asset->set_language (dcp::LanguageTag("de-DE"));
1958 asset->write (dir / "subs.mxf");
1960 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1961 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1962 check_verify_result (
1966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1967 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1968 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1975 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1977 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1978 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1981 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1982 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1983 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1984 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1986 check_verify_result (
1990 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1996 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1998 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1999 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2002 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2003 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2004 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2006 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2010 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
2012 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
2013 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2016 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2017 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2018 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2019 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2021 check_verify_result (
2025 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
2026 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2031 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
2033 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
2034 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2037 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2038 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2039 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2040 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2042 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2046 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2048 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2049 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2052 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2054 check_verify_result (
2058 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
2059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2064 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2066 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2067 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2070 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2072 check_verify_result (
2076 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2077 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2082 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2084 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2085 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2088 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2089 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2090 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2091 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2093 check_verify_result (
2097 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2098 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2103 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2105 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2106 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2109 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2110 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2111 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2113 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2117 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2119 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2120 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2123 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2124 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2125 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2126 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2128 check_verify_result (
2132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2133 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2138 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2140 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2141 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2144 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2145 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2146 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2147 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2149 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2153 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2155 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2156 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2159 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2161 check_verify_result (
2165 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2170 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2172 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2173 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2176 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2178 check_verify_result (
2182 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2183 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2188 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2190 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2191 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2194 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2195 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2196 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2198 check_verify_result (
2202 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2207 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2209 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2210 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2213 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2214 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2215 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2217 check_verify_result (
2221 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2222 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2227 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2229 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2230 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2233 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2234 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2235 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2237 check_verify_result (
2241 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2246 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2248 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2249 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2252 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2253 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2254 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2256 check_verify_result (
2260 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2265 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2267 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2268 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2269 check_verify_result (
2273 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2274 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2279 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2281 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2282 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2283 check_verify_result (
2287 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2293 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2295 path const dir("build/test/verify_invalid_sound_frame_rate");
2296 prepare_directory (dir);
2298 auto picture = simple_picture (dir, "foo");
2299 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2300 auto reel = make_shared<dcp::Reel>();
2301 reel->add (reel_picture);
2302 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2303 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2304 reel->add (reel_sound);
2305 reel->add (simple_markers());
2306 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2308 auto dcp = make_shared<dcp::DCP>(dir);
2310 dcp->set_annotation_text("hello");
2313 check_verify_result (
2317 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2318 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2323 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2325 path const dir("build/test/verify_missing_cpl_annotation_text");
2326 auto dcp = make_simple (dir);
2329 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2331 auto const cpl = dcp->cpls()[0];
2333 HashCalculator calc(cpl->file().get());
2336 BOOST_REQUIRE (cpl->file());
2337 Editor e(cpl->file().get());
2338 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2341 check_verify_result (
2345 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2346 dcp::VerificationNote(
2347 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get())
2348 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
2353 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2355 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2356 auto dcp = make_simple (dir);
2359 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2360 auto const cpl = dcp->cpls()[0];
2362 HashCalculator calc(cpl->file().get());
2365 BOOST_REQUIRE (cpl->file());
2366 Editor e(cpl->file().get());
2367 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2370 check_verify_result (
2374 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2375 dcp::VerificationNote(
2376 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get())
2377 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash())
2382 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2384 path const dir("build/test/verify_mismatched_asset_duration");
2385 prepare_directory (dir);
2386 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2387 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2389 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2390 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2392 auto reel = make_shared<dcp::Reel>(
2393 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2394 make_shared<dcp::ReelSoundAsset>(ms, 0)
2397 reel->add (simple_markers());
2401 dcp->set_annotation_text("A Test DCP");
2404 check_verify_result (
2408 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2409 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2416 shared_ptr<dcp::CPL>
2417 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2419 prepare_directory (dir);
2420 auto dcp = make_shared<dcp::DCP>(dir);
2421 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2423 auto constexpr reel_length = 192;
2425 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2426 subs->set_language (dcp::LanguageTag("de-DE"));
2427 subs->set_start_time (dcp::Time());
2428 subs->add (simple_subtitle());
2430 subs->write (dir / "subs.mxf");
2431 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2433 auto reel1 = make_shared<dcp::Reel>(
2434 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2435 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2439 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2442 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2443 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2444 reel1->add (markers1);
2448 auto reel2 = make_shared<dcp::Reel>(
2449 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2450 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2454 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2457 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2458 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2459 reel2->add (markers2);
2464 dcp->set_annotation_text("A Test DCP");
2471 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2474 path dir ("build/test/missing_main_subtitle_from_some_reels");
2475 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2476 check_verify_result (
2480 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2487 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2488 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2489 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2493 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2494 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2495 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2501 shared_ptr<dcp::CPL>
2502 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2504 prepare_directory (dir);
2505 auto dcp = make_shared<dcp::DCP>(dir);
2506 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2508 auto constexpr reel_length = 192;
2510 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2511 subs->set_language (dcp::LanguageTag("de-DE"));
2512 subs->set_start_time (dcp::Time());
2513 subs->add (simple_subtitle());
2515 subs->write (dir / "subs.mxf");
2517 auto reel1 = make_shared<dcp::Reel>(
2518 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2519 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2522 for (int i = 0; i < caps_in_reel1; ++i) {
2523 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2526 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2527 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2528 reel1->add (markers1);
2532 auto reel2 = make_shared<dcp::Reel>(
2533 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2534 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2537 for (int i = 0; i < caps_in_reel2; ++i) {
2538 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2541 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2542 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2543 reel2->add (markers2);
2548 dcp->set_annotation_text("A Test DCP");
2555 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2558 path dir ("build/test/mismatched_closed_caption_asset_counts");
2559 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2560 check_verify_result (
2564 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2565 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2570 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2571 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2572 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2576 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2577 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2578 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2585 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2587 prepare_directory (dir);
2588 auto dcp = make_shared<dcp::DCP>(dir);
2589 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2591 auto constexpr reel_length = 192;
2593 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2594 subs->set_language (dcp::LanguageTag("de-DE"));
2595 subs->set_start_time (dcp::Time());
2596 subs->add (simple_subtitle());
2598 subs->write (dir / "subs.mxf");
2599 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2602 auto reel = make_shared<dcp::Reel>(
2603 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2604 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2607 reel->add (reel_text);
2609 reel->add (simple_markers(reel_length));
2614 dcp->set_annotation_text("A Test DCP");
2617 check_verify_result (
2621 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2622 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2627 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2629 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2630 "build/test/verify_subtitle_entry_point_must_be_present",
2631 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2632 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2633 asset->unset_entry_point ();
2637 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2638 "build/test/verify_subtitle_entry_point_must_be_zero",
2639 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2640 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2641 asset->set_entry_point (4);
2645 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2646 "build/test/verify_closed_caption_entry_point_must_be_present",
2647 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2648 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2649 asset->unset_entry_point ();
2653 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2654 "build/test/verify_closed_caption_entry_point_must_be_zero",
2655 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2656 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2657 asset->set_entry_point (9);
2663 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2667 path const dir("build/test/verify_missing_hash");
2668 auto dcp = make_simple (dir);
2671 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2672 auto const cpl = dcp->cpls()[0];
2673 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2674 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2675 auto asset_id = cpl->reels()[0]->main_picture()->id();
2677 HashCalculator calc(cpl->file().get());
2680 BOOST_REQUIRE (cpl->file());
2681 Editor e(cpl->file().get());
2682 e.delete_first_line_containing("<Hash>");
2685 check_verify_result (
2689 dcp::VerificationNote(
2690 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2691 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2699 verify_markers_test (
2701 vector<pair<dcp::Marker, dcp::Time>> markers,
2702 vector<dcp::VerificationNote> test_notes
2705 auto dcp = make_simple (dir);
2706 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2707 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2708 for (auto const& i: markers) {
2709 markers_asset->set (i.first, i.second);
2711 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2714 check_verify_result({dir}, {}, test_notes);
2718 BOOST_AUTO_TEST_CASE (verify_markers)
2720 verify_markers_test (
2721 "build/test/verify_markers_all_correct",
2723 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2724 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2725 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2726 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2731 verify_markers_test (
2732 "build/test/verify_markers_missing_ffec",
2734 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2735 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2736 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2739 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2742 verify_markers_test (
2743 "build/test/verify_markers_missing_ffmc",
2745 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2746 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2747 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2753 verify_markers_test (
2754 "build/test/verify_markers_missing_ffoc",
2756 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2757 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2758 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2761 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2764 verify_markers_test (
2765 "build/test/verify_markers_missing_lfoc",
2767 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2768 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2769 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2772 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2775 verify_markers_test (
2776 "build/test/verify_markers_incorrect_ffoc",
2778 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2779 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2780 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2781 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2784 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2787 verify_markers_test (
2788 "build/test/verify_markers_incorrect_lfoc",
2790 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2791 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2792 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2793 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2796 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2801 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2803 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2804 prepare_directory (dir);
2805 auto dcp = make_simple (dir);
2806 auto cpl = dcp->cpls()[0];
2807 cpl->unset_version_number();
2810 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2814 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2816 path dir = "build/test/verify_missing_extension_metadata1";
2817 auto dcp = make_simple (dir);
2820 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2821 auto cpl = dcp->cpls()[0];
2823 HashCalculator calc(cpl->file().get());
2826 Editor e (cpl->file().get());
2827 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2830 check_verify_result (
2834 dcp::VerificationNote(
2835 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2836 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2837 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2842 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2844 path dir = "build/test/verify_missing_extension_metadata2";
2845 auto dcp = make_simple (dir);
2848 auto cpl = dcp->cpls()[0];
2850 HashCalculator calc(cpl->file().get());
2853 Editor e (cpl->file().get());
2854 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2857 check_verify_result (
2861 dcp::VerificationNote(
2862 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2863 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2869 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2871 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2872 auto dcp = make_simple (dir);
2875 auto const cpl = dcp->cpls()[0];
2877 HashCalculator calc(cpl->file().get());
2880 Editor e (cpl->file().get());
2881 e.replace ("<meta:Name>A", "<meta:NameX>A");
2882 e.replace ("n</meta:Name>", "n</meta:NameX>");
2885 check_verify_result (
2889 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2890 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 },
2891 dcp::VerificationNote(
2892 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2893 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2898 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2900 path dir = "build/test/verify_invalid_extension_metadata1";
2901 auto dcp = make_simple (dir);
2904 auto cpl = dcp->cpls()[0];
2906 HashCalculator calc(cpl->file().get());
2909 Editor e (cpl->file().get());
2910 e.replace ("Application", "Fred");
2913 check_verify_result (
2917 dcp::VerificationNote(
2918 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2919 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2920 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2925 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2927 path dir = "build/test/verify_invalid_extension_metadata2";
2928 auto dcp = make_simple (dir);
2931 auto cpl = dcp->cpls()[0];
2933 HashCalculator calc(cpl->file().get());
2936 Editor e (cpl->file().get());
2937 e.replace ("DCP Constraints Profile", "Fred");
2940 check_verify_result (
2944 dcp::VerificationNote(
2945 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2946 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2952 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2954 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2955 auto dcp = make_simple (dir);
2958 auto const cpl = dcp->cpls()[0];
2960 HashCalculator calc(cpl->file().get());
2963 Editor e (cpl->file().get());
2964 e.replace ("<meta:Value>", "<meta:ValueX>");
2965 e.replace ("</meta:Value>", "</meta:ValueX>");
2968 check_verify_result (
2972 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2973 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 75 },
2974 dcp::VerificationNote(
2975 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
2976 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
2981 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2983 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2984 auto dcp = make_simple (dir);
2987 auto const cpl = dcp->cpls()[0];
2989 HashCalculator calc(cpl->file().get());
2992 Editor e (cpl->file().get());
2993 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2996 check_verify_result (
3000 dcp::VerificationNote(
3001 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
3002 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3003 { 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() },
3008 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
3010 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
3011 auto dcp = make_simple (dir);
3014 auto const cpl = dcp->cpls()[0];
3016 HashCalculator calc(cpl->file().get());
3019 Editor e (cpl->file().get());
3020 e.replace ("<meta:Property>", "<meta:PropertyX>");
3021 e.replace ("</meta:Property>", "</meta:PropertyX>");
3024 check_verify_result (
3028 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
3029 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
3030 dcp::VerificationNote(
3031 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
3032 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3037 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
3039 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
3040 auto dcp = make_simple (dir);
3043 auto const cpl = dcp->cpls()[0];
3045 HashCalculator calc(cpl->file().get());
3048 Editor e (cpl->file().get());
3049 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
3050 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
3053 check_verify_result (
3057 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
3058 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 },
3059 dcp::VerificationNote(
3060 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get()
3061 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3067 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
3069 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
3070 prepare_directory (dir);
3071 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3072 copy_file (i.path(), dir / i.path().filename());
3075 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml" );
3076 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
3078 HashCalculator calc(cpl);
3082 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3085 check_verify_result (
3089 dcp::VerificationNote(
3090 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id(), canonical(cpl)
3091 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3092 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl), },
3093 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3094 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3095 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3096 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3097 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
3098 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id(), canonical(cpl) }
3103 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
3105 path dir = "build/test/unsigned_pkl_with_encrypted_content";
3106 prepare_directory (dir);
3107 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3108 copy_file (i.path(), dir / i.path().filename());
3111 path const cpl = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
3112 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
3115 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3118 check_verify_result (
3122 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) },
3123 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3124 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3125 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3126 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3127 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
3128 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) },
3133 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3135 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3136 prepare_directory (dir);
3137 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3138 copy_file (i.path(), dir / i.path().filename());
3142 Editor e (dir / dcp_test1_pkl());
3143 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3146 check_verify_result({dir}, {}, {});
3150 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3152 path dir ("build/test/verify_must_not_be_partially_encrypted");
3153 prepare_directory (dir);
3157 auto signer = make_shared<dcp::CertificateChain>();
3158 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3159 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3160 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3161 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3163 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3167 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3170 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3171 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3172 for (int i = 0; i < 24; ++i) {
3173 writer->write (j2c.data(), j2c.size());
3175 writer->finalize ();
3177 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3179 auto reel = make_shared<dcp::Reel>(
3180 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3181 make_shared<dcp::ReelSoundAsset>(ms, 0)
3184 reel->add (simple_markers());
3188 cpl->set_content_version (
3189 {"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"}
3191 cpl->set_annotation_text ("A Test DCP");
3192 cpl->set_issuer ("OpenDCP 0.0.25");
3193 cpl->set_creator ("OpenDCP 0.0.25");
3194 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3195 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3196 cpl->set_main_sound_sample_rate (48000);
3197 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3198 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3199 cpl->set_version_number (1);
3203 d.set_issuer("OpenDCP 0.0.25");
3204 d.set_creator("OpenDCP 0.0.25");
3205 d.set_issue_date("2012-07-17T04:45:18+00:00");
3206 d.set_annotation_text("A Test DCP");
3207 d.write_xml(signer);
3209 check_verify_result (
3213 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3218 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3220 vector<dcp::VerificationNote> notes;
3221 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"));
3222 auto reader = picture.start_read ();
3223 auto frame = reader->get_frame (0);
3224 verify_j2k(frame, 0, 0, 24, notes);
3225 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3229 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3231 vector<dcp::VerificationNote> notes;
3232 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3233 auto reader = picture.start_read ();
3234 auto frame = reader->get_frame (0);
3235 verify_j2k(frame, 0, 0, 24, notes);
3236 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3240 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3242 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3243 prepare_directory (dir);
3244 auto dcp = make_simple (dir);
3246 vector<dcp::VerificationNote> notes;
3247 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3248 auto reader = picture.start_read ();
3249 auto frame = reader->get_frame (0);
3250 verify_j2k(frame, 0, 0, 24, notes);
3251 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3255 /** Check that ResourceID and the XML ID being different is spotted */
3256 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3258 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3259 prepare_directory (dir);
3261 ASDCP::WriterInfo writer_info;
3262 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3265 auto mxf_id = dcp::make_uuid ();
3266 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3267 BOOST_REQUIRE (c == Kumu::UUID_Length);
3269 auto resource_id = dcp::make_uuid ();
3270 ASDCP::TimedText::TimedTextDescriptor descriptor;
3271 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3272 DCP_ASSERT (c == Kumu::UUID_Length);
3274 auto xml_id = dcp::make_uuid ();
3275 ASDCP::TimedText::MXFWriter writer;
3276 auto subs_mxf = dir / "subs.mxf";
3277 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3278 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3279 writer.WriteTimedTextResource (dcp::String::compose(
3280 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3281 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3282 "<Id>urn:uuid:%1</Id>"
3283 "<ContentTitleText>Content</ContentTitleText>"
3284 "<AnnotationText>Annotation</AnnotationText>"
3285 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3286 "<ReelNumber>1</ReelNumber>"
3287 "<Language>en-US</Language>"
3288 "<EditRate>25 1</EditRate>"
3289 "<TimeCodeRate>25</TimeCodeRate>"
3290 "<StartTime>00:00:00:00</StartTime>"
3291 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3293 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3294 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3295 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3304 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3305 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3307 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3309 check_verify_result (
3313 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3314 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3315 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3316 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3321 /** Check that ResourceID and the MXF ID being the same is spotted */
3322 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3324 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3325 prepare_directory (dir);
3327 ASDCP::WriterInfo writer_info;
3328 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3331 auto mxf_id = dcp::make_uuid ();
3332 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3333 BOOST_REQUIRE (c == Kumu::UUID_Length);
3335 auto resource_id = mxf_id;
3336 ASDCP::TimedText::TimedTextDescriptor descriptor;
3337 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3338 DCP_ASSERT (c == Kumu::UUID_Length);
3340 auto xml_id = resource_id;
3341 ASDCP::TimedText::MXFWriter writer;
3342 auto subs_mxf = dir / "subs.mxf";
3343 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3344 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3345 writer.WriteTimedTextResource (dcp::String::compose(
3346 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3347 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3348 "<Id>urn:uuid:%1</Id>"
3349 "<ContentTitleText>Content</ContentTitleText>"
3350 "<AnnotationText>Annotation</AnnotationText>"
3351 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3352 "<ReelNumber>1</ReelNumber>"
3353 "<Language>en-US</Language>"
3354 "<EditRate>25 1</EditRate>"
3355 "<TimeCodeRate>25</TimeCodeRate>"
3356 "<StartTime>00:00:00:00</StartTime>"
3357 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3359 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3360 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3361 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3370 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3371 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3373 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3375 check_verify_result (
3379 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3380 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3381 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3382 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3383 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3388 /** Check a DCP with a 3D asset marked as 2D */
3389 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3391 check_verify_result (
3392 { private_test / "data" / "xm" },
3396 dcp::VerificationNote::Type::WARNING,
3397 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3400 dcp::VerificationNote::Type::BV21_ERROR,
3401 dcp::VerificationNote::Code::INVALID_STANDARD
3408 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3410 path dir = "build/test/verify_unexpected_things_in_main_markers";
3411 prepare_directory (dir);
3412 auto dcp = make_simple (dir, 1, 24);
3415 HashCalculator calc(find_cpl(dir));
3418 Editor e (find_cpl(dir));
3420 " <IntrinsicDuration>24</IntrinsicDuration>",
3421 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3425 dcp::CPL cpl (find_cpl(dir));
3427 check_verify_result (
3431 dcp::VerificationNote(
3432 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir))
3433 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3434 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3435 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3440 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3442 path dir = "build/test/verify_invalid_content_kind";
3443 prepare_directory (dir);
3444 auto dcp = make_simple (dir, 1, 24);
3447 HashCalculator calc(find_cpl(dir));
3450 Editor e(find_cpl(dir));
3451 e.replace("trailer", "trip");
3454 dcp::CPL cpl (find_cpl(dir));
3456 check_verify_result (
3460 dcp::VerificationNote(
3461 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir))
3462 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3463 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3469 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3471 path dir = "build/test/verify_valid_content_kind";
3472 prepare_directory (dir);
3473 auto dcp = make_simple (dir, 1, 24);
3476 HashCalculator calc(find_cpl(dir));
3479 Editor e(find_cpl(dir));
3480 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3483 dcp::CPL cpl (find_cpl(dir));
3485 check_verify_result (
3489 dcp::VerificationNote(
3490 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir))
3491 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3497 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3499 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3500 prepare_directory(dir);
3501 auto dcp = make_simple(dir, 1, 24);
3504 auto constexpr area = "<meta:MainPictureActiveArea>";
3506 HashCalculator calc(find_cpl(dir));
3509 Editor e(find_cpl(dir));
3510 e.delete_lines_after(area, 2);
3511 e.insert(area, "<meta:Height>4080</meta:Height>");
3512 e.insert(area, "<meta:Width>1997</meta:Width>");
3515 dcp::PKL pkl(find_pkl(dir));
3516 dcp::CPL cpl(find_cpl(dir));
3518 check_verify_result(
3522 dcp::VerificationNote(
3523 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir))
3524 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3525 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3526 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 4080 is bigger than the asset height 1080", canonical(find_cpl(dir)) },
3531 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3533 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3534 prepare_directory(dir);
3535 auto dcp = make_simple(dir, 1, 24);
3538 auto constexpr area = "<meta:MainPictureActiveArea>";
3540 HashCalculator calc(find_cpl(dir));
3543 Editor e(find_cpl(dir));
3544 e.delete_lines_after(area, 2);
3545 e.insert(area, "<meta:Height>5125</meta:Height>");
3546 e.insert(area, "<meta:Width>9900</meta:Width>");
3549 dcp::PKL pkl(find_pkl(dir));
3550 dcp::CPL cpl(find_cpl(dir));
3552 check_verify_result(
3556 dcp::VerificationNote(
3557 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir))
3558 ).set_reference_hash(calc.old_hash()).set_calculated_hash(calc.new_hash()),
3559 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3560 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 9900 is bigger than the asset width 1998", canonical(find_cpl(dir)) },
3561 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is bigger than the asset height 1080", canonical(find_cpl(dir)) },
3566 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3570 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3571 prepare_directory(dir);
3572 auto dcp = make_simple(dir, 1, 24);
3576 Editor e(find_pkl(dir));
3577 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3580 dcp::PKL pkl(find_pkl(dir));
3582 check_verify_result(
3586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3591 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3595 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3596 prepare_directory(dir);
3597 auto dcp = make_simple(dir, 1, 24);
3601 Editor e(find_asset_map(dir));
3602 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3605 dcp::PKL pkl(find_pkl(dir));
3606 dcp::AssetMap asset_map(find_asset_map(dir));
3608 check_verify_result(
3612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3613 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3618 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3620 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3622 dcp::MXFMetadata mxf_meta;
3623 mxf_meta.company_name = "OpenDCP";
3624 mxf_meta.product_name = "OpenDCP";
3625 mxf_meta.product_version = "0.0.25";
3627 auto constexpr sample_rate = 48000;
3628 auto constexpr frames = 240;
3630 boost::filesystem::remove_all(path);
3631 boost::filesystem::create_directories(path);
3632 auto dcp = make_shared<dcp::DCP>(path);
3633 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3634 cpl->set_annotation_text("hello");
3635 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3636 cpl->set_main_sound_sample_rate(sample_rate);
3637 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3638 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3639 cpl->set_version_number(1);
3643 /* Reel with 2 channels of audio */
3645 auto mp = simple_picture(path, "1", frames, {});
3646 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3648 auto reel = make_shared<dcp::Reel>(
3649 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3650 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3653 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3654 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3661 /* Reel with 6 channels of audio */
3663 auto mp = simple_picture(path, "2", frames, {});
3664 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3666 auto reel = make_shared<dcp::Reel>(
3667 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3668 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3671 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3672 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3679 dcp->set_annotation_text("hello");
3682 check_verify_result(
3686 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3691 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3693 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3695 dcp::MXFMetadata mxf_meta;
3696 mxf_meta.company_name = "OpenDCP";
3697 mxf_meta.product_name = "OpenDCP";
3698 mxf_meta.product_version = "0.0.25";
3700 auto constexpr sample_rate = 48000;
3701 auto constexpr frames = 240;
3703 boost::filesystem::remove_all(path);
3704 boost::filesystem::create_directories(path);
3705 auto dcp = make_shared<dcp::DCP>(path);
3706 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3707 cpl->set_annotation_text("hello");
3708 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3709 cpl->set_main_sound_sample_rate(sample_rate);
3710 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3711 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3712 cpl->set_version_number(1);
3714 auto mp = simple_picture(path, "1", frames, {});
3715 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3717 auto reel = make_shared<dcp::Reel>(
3718 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3719 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3722 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3723 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3724 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3730 dcp->set_annotation_text("hello");
3733 check_verify_result(
3737 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, std::string{"MainSoundConfiguration has 6 channels but sound assets have 2"}, canonical(find_cpl(path)) },
3742 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3744 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3745 auto constexpr video_frames = 24;
3746 auto constexpr sample_rate = 48000;
3748 boost::filesystem::remove_all(path);
3749 boost::filesystem::create_directories(path);
3751 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3752 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3754 dcp::Size const size(1998, 1080);
3755 auto image = make_shared<dcp::OpenJPEGImage>(size);
3756 boost::random::mt19937 rng(1);
3757 boost::random::uniform_int_distribution<> dist(0, 4095);
3758 for (int c = 0; c < 3; ++c) {
3759 for (int p = 0; p < (1998 * 1080); ++p) {
3760 image->data(c)[p] = dist(rng);
3763 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3764 for (int i = 0; i < 24; ++i) {
3765 picture_writer->write(j2c.data(), j2c.size());
3767 picture_writer->finalize();
3769 auto dcp = make_shared<dcp::DCP>(path);
3770 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3771 cpl->set_content_version(
3772 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3774 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3775 cpl->set_main_sound_sample_rate(sample_rate);
3776 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3777 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3778 cpl->set_version_number(1);
3780 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3782 auto reel = make_shared<dcp::Reel>(
3783 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3784 make_shared<dcp::ReelSoundAsset>(ms, 0)
3789 dcp->set_annotation_text("A Test DCP");
3792 vector<dcp::VerificationNote> expected;
3794 for (auto frame = 0; frame < 24; frame++) {
3796 dcp::VerificationNote(
3797 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf")
3798 ).set_frame(frame).set_frame_rate(24)
3802 int component_sizes[] = {
3808 for (auto frame = 0; frame < 24; frame++) {
3809 for (auto component = 0; component < 3; component++) {
3811 dcp::VerificationNote(
3812 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
3813 ).set_frame(frame).set_component(component).set_size(component_sizes[component])
3819 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC }
3823 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3826 check_verify_result({ path }, {}, expected);
3830 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3832 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3833 check_verify_result(
3837 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3838 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3839 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3840 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3841 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3842 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3847 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3849 path const dir("build/test/verify_missing_load_font");
3850 prepare_directory (dir);
3851 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3853 Editor editor(dir / "subs.xml");
3854 editor.delete_first_line_containing("LoadFont");
3856 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3857 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3858 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3860 check_verify_result (
3864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3865 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3871 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3873 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3874 prepare_directory(dir);
3875 auto dcp = make_simple (dir, 1, 202);
3878 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3879 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3880 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3881 "<ContentTitleText>Content</ContentTitleText>"
3882 "<AnnotationText>Annotation</AnnotationText>"
3883 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3884 "<ReelNumber>1</ReelNumber>"
3885 "<EditRate>24 1</EditRate>"
3886 "<TimeCodeRate>24</TimeCodeRate>"
3887 "<StartTime>00:00:00:00</StartTime>"
3888 "<Language>de-DE</Language>"
3890 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3891 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3892 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3898 dcp::File xml_file(dir / "subs.xml", "w");
3899 BOOST_REQUIRE(xml_file);
3900 xml_file.write(xml.c_str(), xml.size(), 1);
3902 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3903 subs->write(dir / "subs.mxf");
3905 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3906 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3909 check_verify_result (
3913 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3918 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3920 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3921 boost::filesystem::remove_all(dir);
3923 auto dcp1 = make_simple(dir / "1");
3926 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3928 auto dcp2 = make_simple(dir / "2");
3930 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3932 boost::filesystem::remove(dir / "1" / "video.mxf");
3933 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3935 check_verify_result(
3939 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3944 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3946 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3947 boost::filesystem::remove_all(dir);
3949 auto dcp = make_simple(dir);
3950 BOOST_REQUIRE(dcp->cpls().size() == 1);
3951 auto cpl = dcp->cpls()[0];
3952 cpl->set_content_version(dcp::ContentVersion(""));
3955 check_verify_result(
3959 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())
3964 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
3965 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
3967 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
3969 auto key_id = dcp::make_uuid();
3970 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
3972 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
3973 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
3975 path const pkl_file = find_file(dir, "pkl_");
3976 path const cpl_file = find_file(dir, "cpl_");
3978 check_verify_result(
3982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl_file) },
3983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), canonical(cpl_file) },
3984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file) }