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/test/unit_test.hpp>
62 #include <boost/algorithm/string.hpp>
69 using std::make_shared;
71 using std::shared_ptr;
74 using boost::optional;
75 using namespace boost::filesystem;
78 static list<pair<string, optional<path>>> stages;
80 static string filename_to_id(boost::filesystem::path path)
82 return path.string().substr(4, path.string().length() - 8);
85 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
86 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
88 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
89 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
91 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
93 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
94 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
96 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
97 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
100 stage (string s, optional<path> p)
102 stages.push_back (make_pair (s, p));
112 prepare_directory (path path)
114 using namespace boost::filesystem;
116 create_directories (path);
120 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
121 * to make a new sacrifical test DCP.
124 setup (int reference_number, string verify_test_suffix)
126 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
127 prepare_directory (dir);
128 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
129 copy_file (i.path(), dir / i.path().filename());
138 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
140 auto reel = make_shared<dcp::Reel>();
141 reel->add (reel_asset);
142 reel->add (simple_markers());
144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
146 auto dcp = make_shared<dcp::DCP>(dir);
148 dcp->set_annotation_text("hello");
155 /** Class that can alter a file by searching and replacing strings within it.
156 * On destruction modifies the file whose name was given to the constructor.
164 _content = dcp::file_to_string (_path);
169 auto f = fopen(_path.string().c_str(), "w");
171 fwrite (_content.c_str(), _content.length(), 1, f);
178 ChangeChecker(Editor* editor)
181 _old_content = _editor->_content;
186 BOOST_REQUIRE(_old_content != _editor->_content);
190 std::string _old_content;
193 void replace (string a, string b)
195 ChangeChecker cc(this);
196 boost::algorithm::replace_all (_content, a, b);
199 void delete_first_line_containing (string s)
201 ChangeChecker cc(this);
202 auto lines = as_lines();
205 for (auto i: lines) {
206 if (i.find(s) == string::npos || done) {
207 _content += i + "\n";
214 void delete_lines (string from, string to)
216 ChangeChecker cc(this);
217 auto lines = as_lines();
218 bool deleting = false;
220 for (auto i: lines) {
221 if (i.find(from) != string::npos) {
225 _content += i + "\n";
227 if (deleting && i.find(to) != string::npos) {
233 void insert (string after, string line)
235 ChangeChecker cc(this);
236 auto lines = as_lines();
238 bool replaced = false;
239 for (auto i: lines) {
240 _content += i + "\n";
241 if (!replaced && i.find(after) != string::npos) {
242 _content += line + "\n";
248 void delete_lines_after(string after, int lines_to_delete)
250 ChangeChecker cc(this);
251 auto lines = as_lines();
253 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
254 return line.find(after) != string::npos;
257 for (auto i = lines.begin(); i != lines.end(); ++i) {
259 to_delete = lines_to_delete;
260 _content += *i + "\n";
261 } else if (to_delete == 0) {
262 _content += *i + "\n";
270 friend class ChangeChecker;
272 vector<string> as_lines() const
274 vector<string> lines;
275 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
280 std::string _content;
284 LIBDCP_DISABLE_WARNINGS
287 dump_notes (vector<dcp::VerificationNote> const & notes)
289 for (auto i: notes) {
290 std::cout << dcp::note_to_string(i) << "\n";
293 LIBDCP_ENABLE_WARNINGS
298 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
300 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
301 std::sort (notes.begin(), notes.end());
302 std::sort (test_notes.begin(), test_notes.end());
304 string message = "\nVerification notes from test:\n";
305 for (auto i: notes) {
306 message += " " + note_to_string(i) + "\n";
307 message += dcp::String::compose(
308 " [%1 %2 %3 %4 %5]\n",
309 static_cast<int>(i.type()),
310 static_cast<int>(i.code()),
311 i.note().get_value_or("<none>"),
312 i.file().get_value_or("<none>"),
313 i.line().get_value_or(0)
316 message += "Expected:\n";
317 for (auto i: test_notes) {
318 message += " " + note_to_string(i) + "\n";
319 message += dcp::String::compose(
320 " [%1 %2 %3 %4 %5]\n",
321 static_cast<int>(i.type()),
322 static_cast<int>(i.code()),
323 i.note().get_value_or("<none>"),
324 i.file().get_value_or("<none>"),
325 i.line().get_value_or(0)
329 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
333 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
334 * replacing from with to. Verify the resulting DCP and check that the results match the given
339 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
341 auto dir = setup (1, suffix);
344 Editor e (file(suffix));
345 e.replace (from, to);
348 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
350 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
351 auto i = notes.begin();
352 auto j = codes.begin();
353 while (i != notes.end()) {
354 BOOST_CHECK_EQUAL (i->code(), *j);
361 BOOST_AUTO_TEST_CASE (verify_no_error)
364 auto dir = setup (1, "no_error");
365 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
367 path const cpl_file = dir / dcp_test1_cpl;
368 path const pkl_file = dir / dcp_test1_pkl;
369 path const assetmap_file = dir / "ASSETMAP.xml";
371 auto st = stages.begin();
372 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
373 BOOST_REQUIRE (st->second);
374 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
376 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
377 BOOST_REQUIRE (st->second);
378 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
380 BOOST_CHECK_EQUAL (st->first, "Checking reel");
381 BOOST_REQUIRE (!st->second);
383 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
384 BOOST_REQUIRE (st->second);
385 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
387 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
388 BOOST_REQUIRE (st->second);
389 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
391 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
392 BOOST_REQUIRE (st->second);
393 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
395 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
396 BOOST_REQUIRE (st->second);
397 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
399 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
400 BOOST_REQUIRE (st->second);
401 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
403 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
404 BOOST_REQUIRE (st->second);
405 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
407 BOOST_REQUIRE (st == stages.end());
409 BOOST_CHECK_EQUAL (notes.size(), 0U);
413 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
415 using namespace boost::filesystem;
417 auto dir = setup (1, "incorrect_picture_sound_hash");
419 auto video_path = path(dir / "video.mxf");
420 auto mod = fopen(video_path.string().c_str(), "r+b");
422 fseek (mod, 4096, SEEK_SET);
424 fwrite (&x, sizeof(x), 1, mod);
427 auto audio_path = path(dir / "audio.mxf");
428 mod = fopen(audio_path.string().c_str(), "r+b");
430 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
431 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
434 dcp::ASDCPErrorSuspender sus;
435 check_verify_result (
438 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
439 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
444 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
446 using namespace boost::filesystem;
448 auto dir = setup (1, "mismatched_picture_sound_hashes");
451 Editor e (dir / dcp_test1_pkl);
452 e.replace ("<Hash>", "<Hash>x");
455 check_verify_result (
458 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
459 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
460 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
461 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 },
462 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
463 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 },
468 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
470 auto dir = setup (1, "failed_read_content_kind");
473 Editor e (dir / dcp_test1_cpl);
474 e.replace ("<ContentKind>", "<ContentKind>x");
477 check_verify_result (
480 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
481 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
490 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
498 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
504 asset_map (string suffix)
506 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
510 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
512 check_verify_result_after_replace (
513 "invalid_picture_frame_rate", &cpl,
514 "<FrameRate>24 1", "<FrameRate>99 1",
515 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
516 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
520 BOOST_AUTO_TEST_CASE (verify_missing_asset)
522 auto dir = setup (1, "missing_asset");
523 remove (dir / "video.mxf");
524 check_verify_result (
527 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
532 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
534 check_verify_result_after_replace (
535 "empty_asset_path", &asset_map,
536 "<Path>video.mxf</Path>", "<Path></Path>",
537 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
542 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
544 check_verify_result_after_replace (
545 "mismatched_standard", &cpl,
546 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
547 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
548 dcp::VerificationNote::Code::INVALID_XML,
549 dcp::VerificationNote::Code::INVALID_XML,
550 dcp::VerificationNote::Code::INVALID_XML,
551 dcp::VerificationNote::Code::INVALID_XML,
552 dcp::VerificationNote::Code::INVALID_XML,
553 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
558 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
560 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
561 check_verify_result_after_replace (
562 "invalid_xml_cpl_id", &cpl,
563 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
564 { dcp::VerificationNote::Code::INVALID_XML }
569 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
571 check_verify_result_after_replace (
572 "invalid_xml_issue_date", &cpl,
573 "<IssueDate>", "<IssueDate>x",
574 { dcp::VerificationNote::Code::INVALID_XML,
575 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
580 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
582 check_verify_result_after_replace (
583 "invalid_xml_pkl_id", &pkl,
584 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
585 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
586 { dcp::VerificationNote::Code::INVALID_XML }
591 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
593 check_verify_result_after_replace (
594 "invalid_xml_asset_map_id", &asset_map,
595 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
596 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
597 { dcp::VerificationNote::Code::INVALID_XML }
602 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
605 auto dir = setup (3, "verify_invalid_standard");
606 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
608 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
609 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
610 path const assetmap_file = dir / "ASSETMAP";
612 auto st = stages.begin();
613 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
614 BOOST_REQUIRE (st->second);
615 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
617 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
618 BOOST_REQUIRE (st->second);
619 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
621 BOOST_CHECK_EQUAL (st->first, "Checking reel");
622 BOOST_REQUIRE (!st->second);
624 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
625 BOOST_REQUIRE (st->second);
626 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
628 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
629 BOOST_REQUIRE (st->second);
630 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
632 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
633 BOOST_REQUIRE (st->second);
634 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
636 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
637 BOOST_REQUIRE (st->second);
638 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
640 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
641 BOOST_REQUIRE (st->second);
642 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
644 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
645 BOOST_REQUIRE (st->second);
646 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
648 BOOST_REQUIRE (st == stages.end());
650 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
651 auto i = notes.begin ();
652 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
653 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
655 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
656 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
659 /* DCP with a short asset */
660 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
662 auto dir = setup (8, "invalid_duration");
663 check_verify_result (
666 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
667 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
670 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
678 dcp_from_frame (dcp::ArrayData const& frame, path dir)
680 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
681 create_directories (dir);
682 auto writer = asset->start_write (dir / "pic.mxf", true);
683 for (int i = 0; i < 24; ++i) {
684 writer->write (frame.data(), frame.size());
688 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
689 return write_dcp_with_single_asset (dir, reel_asset);
693 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
695 int const too_big = 1302083 * 2;
697 /* Compress a black image */
698 auto image = black_image ();
699 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
700 BOOST_REQUIRE (frame.size() < too_big);
702 /* Place it in a bigger block with some zero padding at the end */
703 dcp::ArrayData oversized_frame(too_big);
704 memcpy (oversized_frame.data(), frame.data(), frame.size());
705 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
707 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
708 prepare_directory (dir);
709 auto cpl = dcp_from_frame (oversized_frame, dir);
711 check_verify_result (
714 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
721 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
723 int const nearly_too_big = 1302083 * 0.98;
725 /* Compress a black image */
726 auto image = black_image ();
727 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
728 BOOST_REQUIRE (frame.size() < nearly_too_big);
730 /* Place it in a bigger block with some zero padding at the end */
731 dcp::ArrayData oversized_frame(nearly_too_big);
732 memcpy (oversized_frame.data(), frame.data(), frame.size());
733 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
735 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
736 prepare_directory (dir);
737 auto cpl = dcp_from_frame (oversized_frame, dir);
739 check_verify_result (
742 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
743 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
744 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
749 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
751 /* Compress a black image */
752 auto image = black_image ();
753 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
754 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
756 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
757 prepare_directory (dir);
758 auto cpl = dcp_from_frame (frame, dir);
760 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
764 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
766 path const dir("build/test/verify_valid_interop_subtitles");
767 prepare_directory (dir);
768 copy_file ("test/data/subs1.xml", dir / "subs.xml");
769 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
770 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
771 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
773 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
777 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
779 using namespace boost::filesystem;
781 path const dir("build/test/verify_invalid_interop_subtitles");
782 prepare_directory (dir);
783 copy_file ("test/data/subs1.xml", dir / "subs.xml");
784 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
785 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
786 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
789 Editor e (dir / "subs.xml");
790 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
793 check_verify_result (
796 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
799 dcp::VerificationNote::Type::ERROR,
800 dcp::VerificationNote::Code::INVALID_XML,
801 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
809 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
811 path const dir("build/test/verify_valid_smpte_subtitles");
812 prepare_directory (dir);
813 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
814 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
815 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
816 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
818 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
822 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
824 using namespace boost::filesystem;
826 path const dir("build/test/verify_invalid_smpte_subtitles");
827 prepare_directory (dir);
828 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
829 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
830 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
831 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
832 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
834 check_verify_result (
837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
839 dcp::VerificationNote::Type::ERROR,
840 dcp::VerificationNote::Code::INVALID_XML,
841 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
845 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
846 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
851 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
853 path const dir("build/test/verify_empty_text_node_in_subtitles");
854 prepare_directory (dir);
855 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
856 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
857 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
858 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
860 check_verify_result (
863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
864 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
871 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
872 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
874 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
875 prepare_directory (dir);
876 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
877 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
878 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
879 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
881 check_verify_result (
884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
889 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
890 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
892 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
893 prepare_directory (dir);
894 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
895 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
896 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
897 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
899 check_verify_result (
902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
908 BOOST_AUTO_TEST_CASE (verify_external_asset)
910 path const ov_dir("build/test/verify_external_asset");
911 prepare_directory (ov_dir);
913 auto image = black_image ();
914 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
915 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
916 dcp_from_frame (frame, ov_dir);
918 dcp::DCP ov (ov_dir);
921 path const vf_dir("build/test/verify_external_asset_vf");
922 prepare_directory (vf_dir);
924 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
925 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
927 check_verify_result (
930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
936 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
938 path const dir("build/test/verify_valid_cpl_metadata");
939 prepare_directory (dir);
941 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
942 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
943 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
945 auto reel = make_shared<dcp::Reel>();
946 reel->add (reel_asset);
948 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
949 reel->add (simple_markers(16 * 24));
951 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
953 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
954 cpl->set_main_sound_sample_rate (48000);
955 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
956 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
957 cpl->set_version_number (1);
961 dcp.set_annotation_text("hello");
967 find_prefix(path dir, string prefix)
969 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
970 return boost::starts_with(p.filename().string(), prefix);
973 BOOST_REQUIRE(iter != directory_iterator());
978 path find_cpl (path dir)
980 return find_prefix(dir, "cpl_");
987 return find_prefix(dir, "pkl_");
992 find_asset_map(path dir)
994 return find_prefix(dir, "ASSETMAP");
998 /* DCP with invalid CompositionMetadataAsset */
999 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1001 using namespace boost::filesystem;
1003 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1004 prepare_directory (dir);
1006 auto reel = make_shared<dcp::Reel>();
1007 reel->add (black_picture_asset(dir));
1008 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1010 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1011 cpl->set_main_sound_sample_rate (48000);
1012 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1013 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1014 cpl->set_version_number (1);
1016 reel->add (simple_markers());
1020 dcp.set_annotation_text("hello");
1024 Editor e (find_cpl(dir));
1025 e.replace ("MainSound", "MainSoundX");
1028 check_verify_result (
1031 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1032 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1034 dcp::VerificationNote::Type::ERROR,
1035 dcp::VerificationNote::Code::INVALID_XML,
1036 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1037 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1038 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1039 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1040 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1041 "ExtensionMetadataList?,)'"),
1042 canonical(cpl->file().get()),
1045 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1050 /* DCP with invalid CompositionMetadataAsset */
1051 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1053 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1054 prepare_directory (dir);
1056 auto reel = make_shared<dcp::Reel>();
1057 reel->add (black_picture_asset(dir));
1058 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1060 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1061 cpl->set_main_sound_sample_rate (48000);
1062 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1063 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1067 dcp.set_annotation_text("hello");
1071 Editor e (find_cpl(dir));
1072 e.replace ("meta:Width", "meta:WidthX");
1075 check_verify_result (
1077 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1082 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1084 path const dir("build/test/verify_invalid_language1");
1085 prepare_directory (dir);
1086 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1087 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1088 asset->_language = "wrong-andbad";
1089 asset->write (dir / "subs.mxf");
1090 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1091 reel_asset->_language = "badlang";
1092 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1094 check_verify_result (
1097 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1098 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1099 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1104 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1105 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1107 path const dir("build/test/verify_invalid_language2");
1108 prepare_directory (dir);
1109 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1110 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1111 asset->_language = "wrong-andbad";
1112 asset->write (dir / "subs.mxf");
1113 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1114 reel_asset->_language = "badlang";
1115 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1117 check_verify_result (
1120 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1121 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1122 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1127 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1128 * the release territory.
1130 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1132 path const dir("build/test/verify_invalid_language3");
1133 prepare_directory (dir);
1135 auto picture = simple_picture (dir, "foo");
1136 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1137 auto reel = make_shared<dcp::Reel>();
1138 reel->add (reel_picture);
1139 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1140 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1141 reel->add (reel_sound);
1142 reel->add (simple_markers());
1144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1146 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1147 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1148 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1149 cpl->set_main_sound_sample_rate (48000);
1150 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1151 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1152 cpl->set_version_number (1);
1153 cpl->_release_territory = "fred-jim";
1154 auto dcp = make_shared<dcp::DCP>(dir);
1156 dcp->set_annotation_text("hello");
1159 check_verify_result (
1162 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1163 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1164 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1165 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1171 vector<dcp::VerificationNote>
1172 check_picture_size (int width, int height, int frame_rate, bool three_d)
1174 using namespace boost::filesystem;
1176 path dcp_path = "build/test/verify_picture_test";
1177 prepare_directory (dcp_path);
1179 shared_ptr<dcp::PictureAsset> mp;
1181 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1183 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1185 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1187 auto image = black_image (dcp::Size(width, height));
1188 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1189 int const length = three_d ? frame_rate * 2 : frame_rate;
1190 for (int i = 0; i < length; ++i) {
1191 picture_writer->write (j2c.data(), j2c.size());
1193 picture_writer->finalize ();
1195 auto d = make_shared<dcp::DCP>(dcp_path);
1196 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1197 cpl->set_annotation_text ("A Test DCP");
1198 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1199 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1200 cpl->set_main_sound_sample_rate (48000);
1201 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1202 cpl->set_main_picture_active_area(dcp::Size(width, height));
1203 cpl->set_version_number (1);
1205 auto reel = make_shared<dcp::Reel>();
1208 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1210 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1213 reel->add (simple_markers(frame_rate));
1218 d->set_annotation_text("A Test DCP");
1221 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1227 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1229 auto notes = check_picture_size(width, height, frame_rate, three_d);
1230 BOOST_CHECK_EQUAL (notes.size(), 0U);
1236 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1238 auto notes = check_picture_size(width, height, frame_rate, three_d);
1239 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1240 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1241 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1247 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1249 auto notes = check_picture_size(width, height, frame_rate, three_d);
1250 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1251 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1252 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1258 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1260 auto notes = check_picture_size(width, height, frame_rate, three_d);
1261 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1262 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1263 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1267 BOOST_AUTO_TEST_CASE (verify_picture_size)
1269 using namespace boost::filesystem;
1272 check_picture_size_ok (2048, 858, 24, false);
1273 check_picture_size_ok (2048, 858, 25, false);
1274 check_picture_size_ok (2048, 858, 48, false);
1275 check_picture_size_ok (2048, 858, 24, true);
1276 check_picture_size_ok (2048, 858, 25, true);
1277 check_picture_size_ok (2048, 858, 48, true);
1280 check_picture_size_ok (1998, 1080, 24, false);
1281 check_picture_size_ok (1998, 1080, 25, false);
1282 check_picture_size_ok (1998, 1080, 48, false);
1283 check_picture_size_ok (1998, 1080, 24, true);
1284 check_picture_size_ok (1998, 1080, 25, true);
1285 check_picture_size_ok (1998, 1080, 48, true);
1288 check_picture_size_ok (4096, 1716, 24, false);
1291 check_picture_size_ok (3996, 2160, 24, false);
1293 /* Bad frame size */
1294 check_picture_size_bad_frame_size (2050, 858, 24, false);
1295 check_picture_size_bad_frame_size (2048, 658, 25, false);
1296 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1297 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1299 /* Bad 2K frame rate */
1300 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1301 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1302 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1304 /* Bad 4K frame rate */
1305 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1306 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1309 auto notes = check_picture_size(3996, 2160, 24, true);
1310 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1311 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1312 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1318 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")
1321 std::make_shared<dcp::SubtitleString>(
1329 dcp::Time(start_frame, 24, 24),
1330 dcp::Time(end_frame, 24, 24),
1332 dcp::HAlign::CENTER,
1336 dcp::Direction::LTR,
1348 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1350 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1351 prepare_directory (dir);
1353 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1354 for (int i = 0; i < 2048; ++i) {
1355 add_test_subtitle (asset, i * 24, i * 24 + 20);
1357 asset->set_language (dcp::LanguageTag("de-DE"));
1358 asset->write (dir / "subs.mxf");
1359 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1360 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1362 check_verify_result (
1365 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1367 dcp::VerificationNote::Type::BV21_ERROR,
1368 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1370 canonical(dir / "subs.mxf")
1372 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1373 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1379 shared_ptr<dcp::SMPTESubtitleAsset>
1380 make_large_subtitle_asset (path font_file)
1382 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1383 dcp::ArrayData big_fake_font(1024 * 1024);
1384 big_fake_font.write (font_file);
1385 for (int i = 0; i < 116; ++i) {
1386 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1394 verify_timed_text_asset_too_large (string name)
1396 auto const dir = path("build/test") / name;
1397 prepare_directory (dir);
1398 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1399 add_test_subtitle (asset, 0, 240);
1400 asset->set_language (dcp::LanguageTag("de-DE"));
1401 asset->write (dir / "subs.mxf");
1403 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1404 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1406 check_verify_result (
1409 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1410 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1411 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1412 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1413 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1418 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1420 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1421 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1425 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1427 path dir = "build/test/verify_missing_subtitle_language";
1428 prepare_directory (dir);
1429 auto dcp = make_simple (dir, 1, 106);
1432 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1433 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1434 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1435 "<ContentTitleText>Content</ContentTitleText>"
1436 "<AnnotationText>Annotation</AnnotationText>"
1437 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1438 "<ReelNumber>1</ReelNumber>"
1439 "<EditRate>24 1</EditRate>"
1440 "<TimeCodeRate>24</TimeCodeRate>"
1441 "<StartTime>00:00:00:00</StartTime>"
1442 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1444 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1445 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1446 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1452 dcp::File xml_file(dir / "subs.xml", "w");
1453 BOOST_REQUIRE (xml_file);
1454 xml_file.write(xml.c_str(), xml.size(), 1);
1456 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1457 subs->write (dir / "subs.mxf");
1459 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1460 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1461 dcp->set_annotation_text("A Test DCP");
1464 check_verify_result (
1467 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1468 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1473 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1475 path path ("build/test/verify_mismatched_subtitle_languages");
1476 auto constexpr reel_length = 192;
1477 auto dcp = make_simple (path, 2, reel_length);
1478 auto cpl = dcp->cpls()[0];
1481 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1482 subs->set_language (dcp::LanguageTag("de-DE"));
1483 subs->add (simple_subtitle());
1484 subs->write (path / "subs1.mxf");
1485 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1486 cpl->reels()[0]->add(reel_subs);
1490 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1491 subs->set_language (dcp::LanguageTag("en-US"));
1492 subs->add (simple_subtitle());
1493 subs->write (path / "subs2.mxf");
1494 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1495 cpl->reels()[1]->add(reel_subs);
1498 dcp->set_annotation_text("A Test DCP");
1501 check_verify_result (
1504 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1505 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1506 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1511 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1513 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1514 auto constexpr reel_length = 192;
1515 auto dcp = make_simple (path, 2, reel_length);
1516 auto cpl = dcp->cpls()[0];
1519 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1520 ccaps->set_language (dcp::LanguageTag("de-DE"));
1521 ccaps->add (simple_subtitle());
1522 ccaps->write (path / "subs1.mxf");
1523 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1524 cpl->reels()[0]->add(reel_ccaps);
1528 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1529 ccaps->set_language (dcp::LanguageTag("en-US"));
1530 ccaps->add (simple_subtitle());
1531 ccaps->write (path / "subs2.mxf");
1532 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1533 cpl->reels()[1]->add(reel_ccaps);
1536 dcp->set_annotation_text("A Test DCP");
1539 check_verify_result (
1542 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1543 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1548 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1550 path dir = "build/test/verify_missing_subtitle_start_time";
1551 prepare_directory (dir);
1552 auto dcp = make_simple (dir, 1, 106);
1555 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1556 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1557 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1558 "<ContentTitleText>Content</ContentTitleText>"
1559 "<AnnotationText>Annotation</AnnotationText>"
1560 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1561 "<ReelNumber>1</ReelNumber>"
1562 "<Language>de-DE</Language>"
1563 "<EditRate>24 1</EditRate>"
1564 "<TimeCodeRate>24</TimeCodeRate>"
1565 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1567 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1568 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1569 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1575 dcp::File xml_file(dir / "subs.xml", "w");
1576 BOOST_REQUIRE (xml_file);
1577 xml_file.write(xml.c_str(), xml.size(), 1);
1579 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1580 subs->write (dir / "subs.mxf");
1582 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1583 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1584 dcp->set_annotation_text("A Test DCP");
1587 check_verify_result (
1590 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1591 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1596 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1598 path dir = "build/test/verify_invalid_subtitle_start_time";
1599 prepare_directory (dir);
1600 auto dcp = make_simple (dir, 1, 106);
1603 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1604 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1605 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1606 "<ContentTitleText>Content</ContentTitleText>"
1607 "<AnnotationText>Annotation</AnnotationText>"
1608 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1609 "<ReelNumber>1</ReelNumber>"
1610 "<Language>de-DE</Language>"
1611 "<EditRate>24 1</EditRate>"
1612 "<TimeCodeRate>24</TimeCodeRate>"
1613 "<StartTime>00:00:02:00</StartTime>"
1614 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1616 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1617 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1618 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1624 dcp::File xml_file(dir / "subs.xml", "w");
1625 BOOST_REQUIRE (xml_file);
1626 xml_file.write(xml.c_str(), xml.size(), 1);
1628 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1629 subs->write (dir / "subs.mxf");
1631 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1632 dcp->cpls().front()->reels().front()->add(reel_subs);
1633 dcp->set_annotation_text("A Test DCP");
1636 check_verify_result (
1639 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1640 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1648 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1651 , v_position(v_position_)
1659 dcp::VAlign v_align;
1665 shared_ptr<dcp::CPL>
1666 dcp_with_text (path dir, vector<TestText> subs)
1668 prepare_directory (dir);
1669 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1670 asset->set_start_time (dcp::Time());
1671 for (auto i: subs) {
1672 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1674 asset->set_language (dcp::LanguageTag("de-DE"));
1675 asset->write (dir / "subs.mxf");
1677 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1678 return write_dcp_with_single_asset (dir, reel_asset);
1683 shared_ptr<dcp::CPL>
1684 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1686 prepare_directory (dir);
1687 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1688 asset->set_start_time (dcp::Time());
1689 asset->set_language (dcp::LanguageTag("de-DE"));
1691 auto subs_mxf = dir / "subs.mxf";
1692 asset->write (subs_mxf);
1694 /* The call to write() puts the asset into the DCP correctly but it will have
1695 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1698 ASDCP::TimedText::MXFWriter writer;
1699 ASDCP::WriterInfo writer_info;
1700 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1702 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1703 DCP_ASSERT (c == Kumu::UUID_Length);
1704 ASDCP::TimedText::TimedTextDescriptor descriptor;
1705 descriptor.ContainerDuration = asset->intrinsic_duration();
1706 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1707 DCP_ASSERT (c == Kumu::UUID_Length);
1708 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1709 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1710 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1711 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1714 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1715 return write_dcp_with_single_asset (dir, reel_asset);
1719 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1721 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1722 /* Just too early */
1723 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1724 check_verify_result (
1727 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1734 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1736 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1737 /* Just late enough */
1738 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1739 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1743 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1745 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1746 prepare_directory (dir);
1748 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1749 asset1->set_start_time (dcp::Time());
1750 /* Just late enough */
1751 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1752 asset1->set_language (dcp::LanguageTag("de-DE"));
1753 asset1->write (dir / "subs1.mxf");
1754 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1755 auto reel1 = make_shared<dcp::Reel>();
1756 reel1->add (reel_asset1);
1757 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1758 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1759 reel1->add (markers1);
1761 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1762 asset2->set_start_time (dcp::Time());
1763 /* This would be too early on first reel but should be OK on the second */
1764 add_test_subtitle (asset2, 3, 4 * 24);
1765 asset2->set_language (dcp::LanguageTag("de-DE"));
1766 asset2->write (dir / "subs2.mxf");
1767 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1768 auto reel2 = make_shared<dcp::Reel>();
1769 reel2->add (reel_asset2);
1770 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1771 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1772 reel2->add (markers2);
1774 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1777 auto dcp = make_shared<dcp::DCP>(dir);
1779 dcp->set_annotation_text("hello");
1782 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1786 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1788 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1789 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1793 { 5 * 24 + 1, 6 * 24 },
1795 check_verify_result (
1798 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1799 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1804 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1806 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1807 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1811 { 5 * 24 + 16, 8 * 24 },
1813 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1817 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1819 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1820 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1821 check_verify_result (
1824 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1825 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1830 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1832 auto const dir = path("build/test/verify_valid_subtitle_duration");
1833 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1834 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1838 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1840 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1841 prepare_directory (dir);
1842 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1843 asset->set_start_time (dcp::Time());
1844 add_test_subtitle (asset, 0, 4 * 24);
1845 asset->set_language (dcp::LanguageTag("de-DE"));
1846 asset->write (dir / "subs.mxf");
1848 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1849 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1862 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1864 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1865 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1868 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1869 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1870 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1871 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1873 check_verify_result (
1876 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1882 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1884 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1885 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1888 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1889 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1890 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1892 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1896 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1898 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1899 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1902 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1903 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1904 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1905 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1907 check_verify_result (
1910 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1916 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1918 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1919 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1922 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1923 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1924 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1925 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1927 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1931 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1933 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1934 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1937 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1939 check_verify_result (
1942 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1943 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1948 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1950 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1951 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1954 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1956 check_verify_result (
1959 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1960 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1965 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1967 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1968 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1971 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1972 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1973 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1974 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1976 check_verify_result (
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1985 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1987 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1988 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1991 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1992 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1993 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1995 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1999 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2001 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2002 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2005 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2006 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2007 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2008 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2010 check_verify_result (
2013 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2019 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2021 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2022 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2025 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2026 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2027 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2028 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2030 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2034 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2036 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2037 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2040 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2042 check_verify_result (
2045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2050 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2052 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2053 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2056 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2058 check_verify_result (
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2062 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2067 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2069 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2070 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2073 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2074 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2075 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2077 check_verify_result (
2080 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2085 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2087 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2088 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2091 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2092 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2093 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2095 check_verify_result (
2098 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2099 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2104 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2106 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2107 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2110 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2111 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2112 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2114 check_verify_result (
2117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2122 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2124 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2125 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2128 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2129 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2130 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2132 check_verify_result (
2135 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2140 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2142 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2143 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2144 check_verify_result (
2147 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2148 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2153 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2155 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2156 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2157 check_verify_result (
2160 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2166 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2168 path const dir("build/test/verify_invalid_sound_frame_rate");
2169 prepare_directory (dir);
2171 auto picture = simple_picture (dir, "foo");
2172 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2173 auto reel = make_shared<dcp::Reel>();
2174 reel->add (reel_picture);
2175 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2176 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2177 reel->add (reel_sound);
2178 reel->add (simple_markers());
2179 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2181 auto dcp = make_shared<dcp::DCP>(dir);
2183 dcp->set_annotation_text("hello");
2186 check_verify_result (
2189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2195 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2197 path const dir("build/test/verify_missing_cpl_annotation_text");
2198 auto dcp = make_simple (dir);
2199 dcp->set_annotation_text("A Test DCP");
2202 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2204 auto const cpl = dcp->cpls()[0];
2207 BOOST_REQUIRE (cpl->file());
2208 Editor e(cpl->file().get());
2209 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2216 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2221 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2223 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2224 auto dcp = make_simple (dir);
2225 dcp->set_annotation_text("A Test DCP");
2228 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2229 auto const cpl = dcp->cpls()[0];
2232 BOOST_REQUIRE (cpl->file());
2233 Editor e(cpl->file().get());
2234 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2237 check_verify_result (
2240 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2241 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2246 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2248 path const dir("build/test/verify_mismatched_asset_duration");
2249 prepare_directory (dir);
2250 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2251 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2253 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2254 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2256 auto reel = make_shared<dcp::Reel>(
2257 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2258 make_shared<dcp::ReelSoundAsset>(ms, 0)
2261 reel->add (simple_markers());
2265 dcp->set_annotation_text("A Test DCP");
2268 check_verify_result (
2271 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2272 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2279 shared_ptr<dcp::CPL>
2280 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2282 prepare_directory (dir);
2283 auto dcp = make_shared<dcp::DCP>(dir);
2284 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2286 auto constexpr reel_length = 192;
2288 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2289 subs->set_language (dcp::LanguageTag("de-DE"));
2290 subs->set_start_time (dcp::Time());
2291 subs->add (simple_subtitle());
2292 subs->write (dir / "subs.mxf");
2293 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2295 auto reel1 = make_shared<dcp::Reel>(
2296 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2297 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2301 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2304 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2305 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2306 reel1->add (markers1);
2310 auto reel2 = make_shared<dcp::Reel>(
2311 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2312 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2316 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2319 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2320 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2321 reel2->add (markers2);
2326 dcp->set_annotation_text("A Test DCP");
2333 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2336 path dir ("build/test/missing_main_subtitle_from_some_reels");
2337 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2338 check_verify_result (
2341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2342 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2348 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2349 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2350 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2354 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2355 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2356 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2362 shared_ptr<dcp::CPL>
2363 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2365 prepare_directory (dir);
2366 auto dcp = make_shared<dcp::DCP>(dir);
2367 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2369 auto constexpr reel_length = 192;
2371 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2372 subs->set_language (dcp::LanguageTag("de-DE"));
2373 subs->set_start_time (dcp::Time());
2374 subs->add (simple_subtitle());
2375 subs->write (dir / "subs.mxf");
2377 auto reel1 = make_shared<dcp::Reel>(
2378 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2379 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2382 for (int i = 0; i < caps_in_reel1; ++i) {
2383 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2386 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2387 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2388 reel1->add (markers1);
2392 auto reel2 = make_shared<dcp::Reel>(
2393 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2394 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2397 for (int i = 0; i < caps_in_reel2; ++i) {
2398 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2401 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2402 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2403 reel2->add (markers2);
2408 dcp->set_annotation_text("A Test DCP");
2415 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2418 path dir ("build/test/mismatched_closed_caption_asset_counts");
2419 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2420 check_verify_result (
2423 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2429 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2430 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2431 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2435 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2436 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2437 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2444 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2446 prepare_directory (dir);
2447 auto dcp = make_shared<dcp::DCP>(dir);
2448 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2450 auto constexpr reel_length = 192;
2452 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2453 subs->set_language (dcp::LanguageTag("de-DE"));
2454 subs->set_start_time (dcp::Time());
2455 subs->add (simple_subtitle());
2456 subs->write (dir / "subs.mxf");
2457 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2460 auto reel = make_shared<dcp::Reel>(
2461 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2462 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2465 reel->add (reel_text);
2467 reel->add (simple_markers(reel_length));
2472 dcp->set_annotation_text("A Test DCP");
2475 check_verify_result (
2478 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2479 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2484 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2486 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2487 "build/test/verify_subtitle_entry_point_must_be_present",
2488 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2489 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2490 asset->unset_entry_point ();
2494 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2495 "build/test/verify_subtitle_entry_point_must_be_zero",
2496 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2497 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2498 asset->set_entry_point (4);
2502 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2503 "build/test/verify_closed_caption_entry_point_must_be_present",
2504 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2505 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2506 asset->unset_entry_point ();
2510 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2511 "build/test/verify_closed_caption_entry_point_must_be_zero",
2512 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2513 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2514 asset->set_entry_point (9);
2520 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2524 path const dir("build/test/verify_missing_hash");
2525 auto dcp = make_simple (dir);
2526 dcp->set_annotation_text("A Test DCP");
2529 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2530 auto const cpl = dcp->cpls()[0];
2531 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2532 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2533 auto asset_id = cpl->reels()[0]->main_picture()->id();
2536 BOOST_REQUIRE (cpl->file());
2537 Editor e(cpl->file().get());
2538 e.delete_first_line_containing("<Hash>");
2541 check_verify_result (
2544 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2552 verify_markers_test (
2554 vector<pair<dcp::Marker, dcp::Time>> markers,
2555 vector<dcp::VerificationNote> test_notes
2558 auto dcp = make_simple (dir);
2559 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2560 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2561 for (auto const& i: markers) {
2562 markers_asset->set (i.first, i.second);
2564 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2565 dcp->set_annotation_text("A Test DCP");
2568 check_verify_result ({dir}, test_notes);
2572 BOOST_AUTO_TEST_CASE (verify_markers)
2574 verify_markers_test (
2575 "build/test/verify_markers_all_correct",
2577 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2578 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2579 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2580 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2585 verify_markers_test (
2586 "build/test/verify_markers_missing_ffec",
2588 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2589 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2590 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2593 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2596 verify_markers_test (
2597 "build/test/verify_markers_missing_ffmc",
2599 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2600 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2601 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2607 verify_markers_test (
2608 "build/test/verify_markers_missing_ffoc",
2610 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2611 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2612 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2615 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2618 verify_markers_test (
2619 "build/test/verify_markers_missing_lfoc",
2621 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2622 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2623 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2626 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2629 verify_markers_test (
2630 "build/test/verify_markers_incorrect_ffoc",
2632 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2633 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2634 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2635 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2638 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2641 verify_markers_test (
2642 "build/test/verify_markers_incorrect_lfoc",
2644 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2645 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2646 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2647 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2650 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2655 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2657 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2658 prepare_directory (dir);
2659 auto dcp = make_simple (dir);
2660 auto cpl = dcp->cpls()[0];
2661 cpl->unset_version_number();
2662 dcp->set_annotation_text("A Test DCP");
2665 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2669 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2671 path dir = "build/test/verify_missing_extension_metadata1";
2672 auto dcp = make_simple (dir);
2673 dcp->set_annotation_text("A Test DCP");
2676 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2677 auto cpl = dcp->cpls()[0];
2680 Editor e (cpl->file().get());
2681 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2684 check_verify_result (
2687 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2688 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2693 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2695 path dir = "build/test/verify_missing_extension_metadata2";
2696 auto dcp = make_simple (dir);
2697 dcp->set_annotation_text("A Test DCP");
2700 auto cpl = dcp->cpls()[0];
2703 Editor e (cpl->file().get());
2704 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2707 check_verify_result (
2710 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2716 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2718 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2719 auto dcp = make_simple (dir);
2720 dcp->set_annotation_text("A Test DCP");
2723 auto const cpl = dcp->cpls()[0];
2726 Editor e (cpl->file().get());
2727 e.replace ("<meta:Name>A", "<meta:NameX>A");
2728 e.replace ("n</meta:Name>", "n</meta:NameX>");
2731 check_verify_result (
2734 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2735 { 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 },
2736 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2741 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2743 path dir = "build/test/verify_invalid_extension_metadata1";
2744 auto dcp = make_simple (dir);
2745 dcp->set_annotation_text("A Test DCP");
2748 auto cpl = dcp->cpls()[0];
2751 Editor e (cpl->file().get());
2752 e.replace ("Application", "Fred");
2755 check_verify_result (
2758 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2759 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2764 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2766 path dir = "build/test/verify_invalid_extension_metadata2";
2767 auto dcp = make_simple (dir);
2768 dcp->set_annotation_text("A Test DCP");
2771 auto cpl = dcp->cpls()[0];
2774 Editor e (cpl->file().get());
2775 e.replace ("DCP Constraints Profile", "Fred");
2778 check_verify_result (
2781 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2782 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2787 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2789 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2790 auto dcp = make_simple (dir);
2791 dcp->set_annotation_text("A Test DCP");
2794 auto const cpl = dcp->cpls()[0];
2797 Editor e (cpl->file().get());
2798 e.replace ("<meta:Value>", "<meta:ValueX>");
2799 e.replace ("</meta:Value>", "</meta:ValueX>");
2802 check_verify_result (
2805 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2806 { 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 },
2807 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2812 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2814 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2815 auto dcp = make_simple (dir);
2816 dcp->set_annotation_text("A Test DCP");
2819 auto const cpl = dcp->cpls()[0];
2822 Editor e (cpl->file().get());
2823 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2826 check_verify_result (
2829 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2830 { 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() },
2835 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2837 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2838 auto dcp = make_simple (dir);
2839 dcp->set_annotation_text("A Test DCP");
2842 auto const cpl = dcp->cpls()[0];
2845 Editor e (cpl->file().get());
2846 e.replace ("<meta:Property>", "<meta:PropertyX>");
2847 e.replace ("</meta:Property>", "</meta:PropertyX>");
2850 check_verify_result (
2853 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2854 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2860 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2862 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2863 auto dcp = make_simple (dir);
2864 dcp->set_annotation_text("A Test DCP");
2867 auto const cpl = dcp->cpls()[0];
2870 Editor e (cpl->file().get());
2871 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2872 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2875 check_verify_result (
2878 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2879 { 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 },
2880 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2886 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2888 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2889 prepare_directory (dir);
2890 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2891 copy_file (i.path(), dir / i.path().filename());
2894 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2895 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2899 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2902 check_verify_result (
2905 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2906 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2907 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2908 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2909 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2910 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2917 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2919 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2920 prepare_directory (dir);
2921 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2922 copy_file (i.path(), dir / i.path().filename());
2925 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2926 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2929 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2932 check_verify_result (
2935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2938 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2939 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2940 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2941 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2946 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2948 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2949 prepare_directory (dir);
2950 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2951 copy_file (i.path(), dir / i.path().filename());
2955 Editor e (dir / dcp_test1_pkl);
2956 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2959 check_verify_result ({dir}, {});
2963 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2965 path dir ("build/test/verify_must_not_be_partially_encrypted");
2966 prepare_directory (dir);
2970 auto signer = make_shared<dcp::CertificateChain>();
2971 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2972 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2973 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2974 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2976 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2980 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2983 auto writer = mp->start_write (dir / "video.mxf", false);
2984 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2985 for (int i = 0; i < 24; ++i) {
2986 writer->write (j2c.data(), j2c.size());
2988 writer->finalize ();
2990 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2992 auto reel = make_shared<dcp::Reel>(
2993 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2994 make_shared<dcp::ReelSoundAsset>(ms, 0)
2997 reel->add (simple_markers());
3001 cpl->set_content_version (
3002 {"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"}
3004 cpl->set_annotation_text ("A Test DCP");
3005 cpl->set_issuer ("OpenDCP 0.0.25");
3006 cpl->set_creator ("OpenDCP 0.0.25");
3007 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3008 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3009 cpl->set_main_sound_sample_rate (48000);
3010 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3011 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3012 cpl->set_version_number (1);
3016 d.set_issuer("OpenDCP 0.0.25");
3017 d.set_creator("OpenDCP 0.0.25");
3018 d.set_issue_date("2012-07-17T04:45:18+00:00");
3019 d.set_annotation_text("A Test DCP");
3020 d.write_xml(signer);
3022 check_verify_result (
3025 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3030 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3032 vector<dcp::VerificationNote> notes;
3033 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"));
3034 auto reader = picture.start_read ();
3035 auto frame = reader->get_frame (0);
3036 verify_j2k (frame, notes);
3037 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3041 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3043 vector<dcp::VerificationNote> notes;
3044 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3045 auto reader = picture.start_read ();
3046 auto frame = reader->get_frame (0);
3047 verify_j2k (frame, notes);
3048 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3052 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3054 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3055 prepare_directory (dir);
3056 auto dcp = make_simple (dir);
3058 vector<dcp::VerificationNote> notes;
3059 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3060 auto reader = picture.start_read ();
3061 auto frame = reader->get_frame (0);
3062 verify_j2k (frame, notes);
3063 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3067 /** Check that ResourceID and the XML ID being different is spotted */
3068 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3070 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3071 prepare_directory (dir);
3073 ASDCP::WriterInfo writer_info;
3074 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3077 auto mxf_id = dcp::make_uuid ();
3078 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3079 BOOST_REQUIRE (c == Kumu::UUID_Length);
3081 auto resource_id = dcp::make_uuid ();
3082 ASDCP::TimedText::TimedTextDescriptor descriptor;
3083 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3084 DCP_ASSERT (c == Kumu::UUID_Length);
3086 auto xml_id = dcp::make_uuid ();
3087 ASDCP::TimedText::MXFWriter writer;
3088 auto subs_mxf = dir / "subs.mxf";
3089 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3090 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3091 writer.WriteTimedTextResource (dcp::String::compose(
3092 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3093 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3094 "<Id>urn:uuid:%1</Id>"
3095 "<ContentTitleText>Content</ContentTitleText>"
3096 "<AnnotationText>Annotation</AnnotationText>"
3097 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3098 "<ReelNumber>1</ReelNumber>"
3099 "<Language>en-US</Language>"
3100 "<EditRate>25 1</EditRate>"
3101 "<TimeCodeRate>25</TimeCodeRate>"
3102 "<StartTime>00:00:00:00</StartTime>"
3104 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3105 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3106 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3115 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3116 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3118 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3120 check_verify_result (
3123 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3124 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3125 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3126 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3131 /** Check that ResourceID and the MXF ID being the same is spotted */
3132 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3134 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3135 prepare_directory (dir);
3137 ASDCP::WriterInfo writer_info;
3138 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3141 auto mxf_id = dcp::make_uuid ();
3142 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3143 BOOST_REQUIRE (c == Kumu::UUID_Length);
3145 auto resource_id = mxf_id;
3146 ASDCP::TimedText::TimedTextDescriptor descriptor;
3147 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3148 DCP_ASSERT (c == Kumu::UUID_Length);
3150 auto xml_id = resource_id;
3151 ASDCP::TimedText::MXFWriter writer;
3152 auto subs_mxf = dir / "subs.mxf";
3153 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3154 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3155 writer.WriteTimedTextResource (dcp::String::compose(
3156 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3157 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3158 "<Id>urn:uuid:%1</Id>"
3159 "<ContentTitleText>Content</ContentTitleText>"
3160 "<AnnotationText>Annotation</AnnotationText>"
3161 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3162 "<ReelNumber>1</ReelNumber>"
3163 "<Language>en-US</Language>"
3164 "<EditRate>25 1</EditRate>"
3165 "<TimeCodeRate>25</TimeCodeRate>"
3166 "<StartTime>00:00:00:00</StartTime>"
3168 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3169 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3170 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3179 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3180 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3182 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3184 check_verify_result (
3187 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3189 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3195 /** Check a DCP with a 3D asset marked as 2D */
3196 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3198 check_verify_result (
3199 { private_test / "data" / "xm" },
3202 dcp::VerificationNote::Type::WARNING,
3203 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3206 dcp::VerificationNote::Type::BV21_ERROR,
3207 dcp::VerificationNote::Code::INVALID_STANDARD
3214 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3216 path dir = "build/test/verify_unexpected_things_in_main_markers";
3217 prepare_directory (dir);
3218 auto dcp = make_simple (dir, 1, 24);
3219 dcp->set_annotation_text("A Test DCP");
3223 Editor e (find_cpl(dir));
3225 " <IntrinsicDuration>24</IntrinsicDuration>",
3226 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3230 dcp::CPL cpl (find_cpl(dir));
3232 check_verify_result (
3235 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3236 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3237 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3242 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3244 path dir = "build/test/verify_invalid_content_kind";
3245 prepare_directory (dir);
3246 auto dcp = make_simple (dir, 1, 24);
3247 dcp->set_annotation_text("A Test DCP");
3251 Editor e(find_cpl(dir));
3252 e.replace("trailer", "trip");
3255 dcp::CPL cpl (find_cpl(dir));
3257 check_verify_result (
3260 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3261 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3267 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3269 path dir = "build/test/verify_valid_content_kind";
3270 prepare_directory (dir);
3271 auto dcp = make_simple (dir, 1, 24);
3272 dcp->set_annotation_text("A Test DCP");
3276 Editor e(find_cpl(dir));
3277 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3280 dcp::CPL cpl (find_cpl(dir));
3282 check_verify_result (
3285 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3291 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3293 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3294 prepare_directory(dir);
3295 auto dcp = make_simple(dir, 1, 24);
3298 auto constexpr area = "<meta:MainPictureActiveArea>";
3301 Editor e(find_cpl(dir));
3302 e.delete_lines_after(area, 2);
3303 e.insert(area, "<meta:Height>4080</meta:Height>");
3304 e.insert(area, "<meta:Width>1997</meta:Width>");
3307 dcp::PKL pkl(find_pkl(dir));
3308 dcp::CPL cpl(find_cpl(dir));
3310 check_verify_result(
3313 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3314 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3315 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3316 { 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)) },
3321 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3323 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3324 prepare_directory(dir);
3325 auto dcp = make_simple(dir, 1, 24);
3328 auto constexpr area = "<meta:MainPictureActiveArea>";
3331 Editor e(find_cpl(dir));
3332 e.delete_lines_after(area, 2);
3333 e.insert(area, "<meta:Height>5125</meta:Height>");
3334 e.insert(area, "<meta:Width>9900</meta:Width>");
3337 dcp::PKL pkl(find_pkl(dir));
3338 dcp::CPL cpl(find_cpl(dir));
3340 check_verify_result(
3343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3344 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3345 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3346 { 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)) },
3347 { 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)) },
3352 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3356 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3357 prepare_directory(dir);
3358 auto dcp = make_simple(dir, 1, 24);
3362 Editor e(find_pkl(dir));
3363 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3366 dcp::PKL pkl(find_pkl(dir));
3368 check_verify_result(
3371 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3376 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3380 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3381 prepare_directory(dir);
3382 auto dcp = make_simple(dir, 1, 24);
3386 Editor e(find_asset_map(dir));
3387 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3390 dcp::PKL pkl(find_pkl(dir));
3391 dcp::AssetMap asset_map(find_asset_map(dir));
3393 check_verify_result(
3396 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3397 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3398 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },