2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
70 using std::make_shared;
72 using std::shared_ptr;
75 using boost::optional;
76 using namespace boost::filesystem;
79 static list<pair<string, optional<path>>> stages;
81 static string filename_to_id(boost::filesystem::path path)
83 return path.string().substr(4, path.string().length() - 8);
86 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
87 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
89 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
90 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
92 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
94 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
95 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
97 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
98 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
101 stage (string s, optional<path> p)
103 stages.push_back (make_pair (s, p));
113 prepare_directory (path path)
115 using namespace boost::filesystem;
117 create_directories (path);
121 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
122 * to make a new sacrificial test DCP.
125 setup (int reference_number, string verify_test_suffix)
127 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
128 prepare_directory (dir);
129 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
130 copy_file (i.path(), dir / i.path().filename());
139 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
141 auto reel = make_shared<dcp::Reel>();
142 reel->add (reel_asset);
143 reel->add (simple_markers());
145 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
147 auto dcp = make_shared<dcp::DCP>(dir);
149 dcp->set_annotation_text("hello");
156 LIBDCP_DISABLE_WARNINGS
159 dump_notes (vector<dcp::VerificationNote> const & notes)
161 for (auto i: notes) {
162 std::cout << dcp::note_to_string(i) << "\n";
165 LIBDCP_ENABLE_WARNINGS
170 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
172 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
173 std::sort (notes.begin(), notes.end());
174 std::sort (test_notes.begin(), test_notes.end());
176 string message = "\nVerification notes from test:\n";
177 for (auto i: notes) {
178 message += " " + note_to_string(i) + "\n";
179 message += dcp::String::compose(
180 " [%1 %2 %3 %4 %5]\n",
181 static_cast<int>(i.type()),
182 static_cast<int>(i.code()),
183 i.note().get_value_or("<none>"),
184 i.file().get_value_or("<none>"),
185 i.line().get_value_or(0)
188 message += "Expected:\n";
189 for (auto i: test_notes) {
190 message += " " + note_to_string(i) + "\n";
191 message += dcp::String::compose(
192 " [%1 %2 %3 %4 %5]\n",
193 static_cast<int>(i.type()),
194 static_cast<int>(i.code()),
195 i.note().get_value_or("<none>"),
196 i.file().get_value_or("<none>"),
197 i.line().get_value_or(0)
201 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
205 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
206 * replacing from with to. Verify the resulting DCP and check that the results match the given
211 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
213 auto dir = setup (1, suffix);
216 Editor e (file(suffix));
217 e.replace (from, to);
220 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
222 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
223 auto i = notes.begin();
224 auto j = codes.begin();
225 while (i != notes.end()) {
226 BOOST_CHECK_EQUAL (i->code(), *j);
235 add_font(shared_ptr<dcp::SubtitleAsset> asset)
237 dcp::ArrayData fake_font(1024);
238 asset->add_font("font", fake_font);
242 BOOST_AUTO_TEST_CASE (verify_no_error)
245 auto dir = setup (1, "no_error");
246 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
248 path const cpl_file = dir / dcp_test1_cpl;
249 path const pkl_file = dir / dcp_test1_pkl;
250 path const assetmap_file = dir / "ASSETMAP.xml";
252 auto st = stages.begin();
253 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
254 BOOST_REQUIRE (st->second);
255 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
257 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
258 BOOST_REQUIRE (st->second);
259 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
261 BOOST_CHECK_EQUAL (st->first, "Checking reel");
262 BOOST_REQUIRE (!st->second);
264 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
265 BOOST_REQUIRE (st->second);
266 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
268 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
269 BOOST_REQUIRE (st->second);
270 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
272 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
273 BOOST_REQUIRE (st->second);
274 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
276 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
277 BOOST_REQUIRE (st->second);
278 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
280 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
281 BOOST_REQUIRE (st->second);
282 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
284 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
285 BOOST_REQUIRE (st->second);
286 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
288 BOOST_REQUIRE (st == stages.end());
290 BOOST_CHECK_EQUAL (notes.size(), 0U);
294 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
296 using namespace boost::filesystem;
298 auto dir = setup (1, "incorrect_picture_sound_hash");
300 auto video_path = path(dir / "video.mxf");
301 auto mod = fopen(video_path.string().c_str(), "r+b");
303 fseek (mod, 4096, SEEK_SET);
305 fwrite (&x, sizeof(x), 1, mod);
308 auto audio_path = path(dir / "audio.mxf");
309 mod = fopen(audio_path.string().c_str(), "r+b");
311 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
312 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
315 dcp::ASDCPErrorSuspender sus;
316 check_verify_result (
319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
320 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
325 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
327 using namespace boost::filesystem;
329 auto dir = setup (1, "mismatched_picture_sound_hashes");
332 Editor e (dir / dcp_test1_pkl);
333 e.replace ("<Hash>", "<Hash>x");
336 check_verify_result (
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 },
349 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
351 auto dir = setup (1, "failed_read_content_kind");
354 Editor e (dir / dcp_test1_cpl);
355 e.replace ("<ContentKind>", "<ContentKind>x");
358 check_verify_result (
361 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
362 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
371 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
379 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
385 asset_map (string suffix)
387 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
391 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
393 check_verify_result_after_replace (
394 "invalid_picture_frame_rate", &cpl,
395 "<FrameRate>24 1", "<FrameRate>99 1",
396 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
397 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
401 BOOST_AUTO_TEST_CASE (verify_missing_asset)
403 auto dir = setup (1, "missing_asset");
404 remove (dir / "video.mxf");
405 check_verify_result (
408 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
413 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
415 check_verify_result_after_replace (
416 "empty_asset_path", &asset_map,
417 "<Path>video.mxf</Path>", "<Path></Path>",
418 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
423 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
425 check_verify_result_after_replace (
426 "mismatched_standard", &cpl,
427 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
428 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::INVALID_XML,
433 dcp::VerificationNote::Code::INVALID_XML,
434 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
439 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
441 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
442 check_verify_result_after_replace (
443 "invalid_xml_cpl_id", &cpl,
444 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
445 { dcp::VerificationNote::Code::INVALID_XML }
450 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
452 check_verify_result_after_replace (
453 "invalid_xml_issue_date", &cpl,
454 "<IssueDate>", "<IssueDate>x",
455 { dcp::VerificationNote::Code::INVALID_XML,
456 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
461 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
463 check_verify_result_after_replace (
464 "invalid_xml_pkl_id", &pkl,
465 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
466 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
467 { dcp::VerificationNote::Code::INVALID_XML }
472 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
474 check_verify_result_after_replace (
475 "invalid_xml_asset_map_id", &asset_map,
476 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
477 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
478 { dcp::VerificationNote::Code::INVALID_XML }
483 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
486 auto dir = setup (3, "verify_invalid_standard");
487 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
489 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
490 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
491 path const assetmap_file = dir / "ASSETMAP";
493 auto st = stages.begin();
494 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
495 BOOST_REQUIRE (st->second);
496 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
498 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
499 BOOST_REQUIRE (st->second);
500 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
502 BOOST_CHECK_EQUAL (st->first, "Checking reel");
503 BOOST_REQUIRE (!st->second);
505 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
506 BOOST_REQUIRE (st->second);
507 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
509 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
510 BOOST_REQUIRE (st->second);
511 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
513 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
514 BOOST_REQUIRE (st->second);
515 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
517 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
518 BOOST_REQUIRE (st->second);
519 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
521 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
522 BOOST_REQUIRE (st->second);
523 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
525 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
526 BOOST_REQUIRE (st->second);
527 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
529 BOOST_REQUIRE (st == stages.end());
531 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
532 auto i = notes.begin ();
533 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
534 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
536 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
537 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
540 /* DCP with a short asset */
541 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
543 auto dir = setup (8, "invalid_duration");
547 BOOST_REQUIRE(dcp.cpls().size() == 1);
548 auto cpl = dcp.cpls()[0];
550 check_verify_result (
553 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
554 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
555 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
556 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
557 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
558 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") },
559 dcp::VerificationNote(
560 dcp::VerificationNote::Type::WARNING,
561 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
563 ).set_id("d74fda30-d5f4-4c5f-870f-ebc089d97eb7")
570 dcp_from_frame (dcp::ArrayData const& frame, path dir)
572 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
573 create_directories (dir);
574 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
575 for (int i = 0; i < 24; ++i) {
576 writer->write (frame.data(), frame.size());
580 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
581 return write_dcp_with_single_asset (dir, reel_asset);
585 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
587 int const too_big = 1302083 * 2;
589 /* Compress a black image */
590 auto image = black_image ();
591 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
592 BOOST_REQUIRE (frame.size() < too_big);
594 /* Place it in a bigger block with some zero padding at the end */
595 dcp::ArrayData oversized_frame(too_big);
596 memcpy (oversized_frame.data(), frame.data(), frame.size());
597 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
599 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
600 prepare_directory (dir);
601 auto cpl = dcp_from_frame (oversized_frame, dir);
603 check_verify_result (
606 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
607 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
608 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
613 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
615 int const nearly_too_big = 1302083 * 0.98;
617 /* Compress a black image */
618 auto image = black_image ();
619 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
620 BOOST_REQUIRE (frame.size() < nearly_too_big);
622 /* Place it in a bigger block with some zero padding at the end */
623 dcp::ArrayData oversized_frame(nearly_too_big);
624 memcpy (oversized_frame.data(), frame.data(), frame.size());
625 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
627 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
628 prepare_directory (dir);
629 auto cpl = dcp_from_frame (oversized_frame, dir);
631 check_verify_result (
634 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
635 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
636 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
641 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
643 /* Compress a black image */
644 auto image = black_image ();
645 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
646 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
648 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
649 prepare_directory (dir);
650 auto cpl = dcp_from_frame (frame, dir);
652 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
656 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
658 path const dir("build/test/verify_valid_interop_subtitles");
659 prepare_directory (dir);
660 copy_file ("test/data/subs1.xml", dir / "subs.xml");
661 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
662 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
663 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
665 check_verify_result (
667 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
673 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
675 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
676 prepare_directory(dir);
677 copy_file("test/data/subs1.xml", dir / "ccap.xml");
678 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
679 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
680 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
682 check_verify_result (
684 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
685 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
690 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
692 using namespace boost::filesystem;
694 path const dir("build/test/verify_invalid_interop_subtitles");
695 prepare_directory (dir);
696 copy_file ("test/data/subs1.xml", dir / "subs.xml");
697 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
698 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
699 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
702 Editor e (dir / "subs.xml");
703 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
706 check_verify_result (
709 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
710 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
712 dcp::VerificationNote::Type::ERROR,
713 dcp::VerificationNote::Code::INVALID_XML,
714 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
718 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
723 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
725 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
726 prepare_directory(dir);
727 copy_file("test/data/subs4.xml", dir / "subs.xml");
728 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
729 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
730 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
732 check_verify_result (
735 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
736 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
737 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
743 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
745 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
746 prepare_directory(dir);
747 copy_file("test/data/subs5.xml", dir / "subs.xml");
748 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
749 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
750 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
752 check_verify_result (
755 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
756 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
762 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
764 path const dir("build/test/verify_valid_smpte_subtitles");
765 prepare_directory (dir);
766 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
767 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
768 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
769 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
775 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
776 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
781 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
783 using namespace boost::filesystem;
785 path const dir("build/test/verify_invalid_smpte_subtitles");
786 prepare_directory (dir);
787 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
788 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
789 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
790 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
791 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
793 check_verify_result (
796 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
798 dcp::VerificationNote::Type::ERROR,
799 dcp::VerificationNote::Code::INVALID_XML,
800 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
804 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
805 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
806 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
812 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
814 path const dir("build/test/verify_empty_text_node_in_subtitles");
815 prepare_directory (dir);
816 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
817 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
818 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
819 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
821 check_verify_result (
824 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
825 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
826 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
828 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
829 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
834 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
835 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
837 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
838 prepare_directory (dir);
839 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
840 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
841 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
842 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
844 check_verify_result (
847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
853 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
854 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
856 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
857 prepare_directory (dir);
858 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
859 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
860 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
861 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
863 check_verify_result (
866 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
868 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
869 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
874 BOOST_AUTO_TEST_CASE (verify_external_asset)
876 path const ov_dir("build/test/verify_external_asset");
877 prepare_directory (ov_dir);
879 auto image = black_image ();
880 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
881 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
882 dcp_from_frame (frame, ov_dir);
884 dcp::DCP ov (ov_dir);
887 path const vf_dir("build/test/verify_external_asset_vf");
888 prepare_directory (vf_dir);
890 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
891 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
893 check_verify_result (
896 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
902 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
904 path const dir("build/test/verify_valid_cpl_metadata");
905 prepare_directory (dir);
907 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
908 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
909 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
911 auto reel = make_shared<dcp::Reel>();
912 reel->add (reel_asset);
914 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
915 reel->add (simple_markers(16 * 24));
917 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
919 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
920 cpl->set_main_sound_sample_rate (48000);
921 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
922 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
923 cpl->set_version_number (1);
927 dcp.set_annotation_text("hello");
933 find_prefix(path dir, string prefix)
935 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
936 return boost::starts_with(p.filename().string(), prefix);
939 BOOST_REQUIRE(iter != directory_iterator());
944 path find_cpl (path dir)
946 return find_prefix(dir, "cpl_");
953 return find_prefix(dir, "pkl_");
958 find_asset_map(path dir)
960 return find_prefix(dir, "ASSETMAP");
964 /* DCP with invalid CompositionMetadataAsset */
965 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
967 using namespace boost::filesystem;
969 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
970 prepare_directory (dir);
972 auto reel = make_shared<dcp::Reel>();
973 reel->add (black_picture_asset(dir));
974 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
976 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
977 cpl->set_main_sound_sample_rate (48000);
978 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
979 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
980 cpl->set_version_number (1);
982 reel->add (simple_markers());
986 dcp.set_annotation_text("hello");
990 Editor e (find_cpl(dir));
991 e.replace ("MainSound", "MainSoundX");
994 check_verify_result (
997 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
998 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1000 dcp::VerificationNote::Type::ERROR,
1001 dcp::VerificationNote::Code::INVALID_XML,
1002 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1003 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1004 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1005 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1006 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1007 "ExtensionMetadataList?,)'"),
1008 canonical(cpl->file().get()),
1011 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1016 /* DCP with invalid CompositionMetadataAsset */
1017 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1019 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1020 prepare_directory (dir);
1022 auto reel = make_shared<dcp::Reel>();
1023 reel->add (black_picture_asset(dir));
1024 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1026 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1027 cpl->set_main_sound_sample_rate (48000);
1028 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1029 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1033 dcp.set_annotation_text("hello");
1037 Editor e (find_cpl(dir));
1038 e.replace ("meta:Width", "meta:WidthX");
1041 check_verify_result (
1043 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1048 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1050 path const dir("build/test/verify_invalid_language1");
1051 prepare_directory (dir);
1052 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1053 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1054 asset->_language = "wrong-andbad";
1055 asset->write (dir / "subs.mxf");
1056 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1057 reel_asset->_language = "badlang";
1058 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1060 check_verify_result (
1063 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1065 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1070 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1071 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1073 path const dir("build/test/verify_invalid_language2");
1074 prepare_directory (dir);
1075 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1076 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1077 asset->_language = "wrong-andbad";
1078 asset->write (dir / "subs.mxf");
1079 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1080 reel_asset->_language = "badlang";
1081 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1083 check_verify_result (
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1093 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1094 * the release territory.
1096 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1098 path const dir("build/test/verify_invalid_language3");
1099 prepare_directory (dir);
1101 auto picture = simple_picture (dir, "foo");
1102 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1103 auto reel = make_shared<dcp::Reel>();
1104 reel->add (reel_picture);
1105 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1106 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1107 reel->add (reel_sound);
1108 reel->add (simple_markers());
1110 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1112 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1113 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1114 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1115 cpl->set_main_sound_sample_rate (48000);
1116 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1117 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1118 cpl->set_version_number (1);
1119 cpl->_release_territory = "fred-jim";
1120 auto dcp = make_shared<dcp::DCP>(dir);
1122 dcp->set_annotation_text("hello");
1125 check_verify_result (
1128 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1129 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1131 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1137 vector<dcp::VerificationNote>
1138 check_picture_size (int width, int height, int frame_rate, bool three_d)
1140 using namespace boost::filesystem;
1142 path dcp_path = "build/test/verify_picture_test";
1143 prepare_directory (dcp_path);
1145 shared_ptr<dcp::PictureAsset> mp;
1147 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1149 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1151 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1153 auto image = black_image (dcp::Size(width, height));
1154 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1155 int const length = three_d ? frame_rate * 2 : frame_rate;
1156 for (int i = 0; i < length; ++i) {
1157 picture_writer->write (j2c.data(), j2c.size());
1159 picture_writer->finalize ();
1161 auto d = make_shared<dcp::DCP>(dcp_path);
1162 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1163 cpl->set_annotation_text ("A Test DCP");
1164 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1165 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1166 cpl->set_main_sound_sample_rate (48000);
1167 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1168 cpl->set_main_picture_active_area(dcp::Size(width, height));
1169 cpl->set_version_number (1);
1171 auto reel = make_shared<dcp::Reel>();
1174 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1176 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1179 reel->add (simple_markers(frame_rate));
1184 d->set_annotation_text("A Test DCP");
1187 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1193 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1195 auto notes = check_picture_size(width, height, frame_rate, three_d);
1196 BOOST_CHECK_EQUAL (notes.size(), 0U);
1202 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1204 auto notes = check_picture_size(width, height, frame_rate, three_d);
1205 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1206 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1207 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1213 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1215 auto notes = check_picture_size(width, height, frame_rate, three_d);
1216 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1217 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1218 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1224 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1226 auto notes = check_picture_size(width, height, frame_rate, three_d);
1227 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1228 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1229 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1233 BOOST_AUTO_TEST_CASE (verify_picture_size)
1235 using namespace boost::filesystem;
1238 check_picture_size_ok (2048, 858, 24, false);
1239 check_picture_size_ok (2048, 858, 25, false);
1240 check_picture_size_ok (2048, 858, 48, false);
1241 check_picture_size_ok (2048, 858, 24, true);
1242 check_picture_size_ok (2048, 858, 25, true);
1243 check_picture_size_ok (2048, 858, 48, true);
1246 check_picture_size_ok (1998, 1080, 24, false);
1247 check_picture_size_ok (1998, 1080, 25, false);
1248 check_picture_size_ok (1998, 1080, 48, false);
1249 check_picture_size_ok (1998, 1080, 24, true);
1250 check_picture_size_ok (1998, 1080, 25, true);
1251 check_picture_size_ok (1998, 1080, 48, true);
1254 check_picture_size_ok (4096, 1716, 24, false);
1257 check_picture_size_ok (3996, 2160, 24, false);
1259 /* Bad frame size */
1260 check_picture_size_bad_frame_size (2050, 858, 24, false);
1261 check_picture_size_bad_frame_size (2048, 658, 25, false);
1262 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1263 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1265 /* Bad 2K frame rate */
1266 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1267 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1268 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1270 /* Bad 4K frame rate */
1271 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1272 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1275 auto notes = check_picture_size(3996, 2160, 24, true);
1276 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1277 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1278 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1284 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")
1287 std::make_shared<dcp::SubtitleString>(
1295 dcp::Time(start_frame, 24, 24),
1296 dcp::Time(end_frame, 24, 24),
1298 dcp::HAlign::CENTER,
1302 dcp::Direction::LTR,
1314 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1316 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1317 prepare_directory (dir);
1319 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1320 for (int i = 0; i < 2048; ++i) {
1321 add_test_subtitle (asset, i * 24, i * 24 + 20);
1324 asset->set_language (dcp::LanguageTag("de-DE"));
1325 asset->write (dir / "subs.mxf");
1326 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1327 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1329 check_verify_result (
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1334 dcp::VerificationNote::Type::BV21_ERROR,
1335 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1337 canonical(dir / "subs.mxf")
1339 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1346 shared_ptr<dcp::SMPTESubtitleAsset>
1347 make_large_subtitle_asset (path font_file)
1349 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1350 dcp::ArrayData big_fake_font(1024 * 1024);
1351 big_fake_font.write (font_file);
1352 for (int i = 0; i < 116; ++i) {
1353 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1361 verify_timed_text_asset_too_large (string name)
1363 auto const dir = path("build/test") / name;
1364 prepare_directory (dir);
1365 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1366 add_test_subtitle (asset, 0, 240);
1367 asset->set_language (dcp::LanguageTag("de-DE"));
1368 asset->write (dir / "subs.mxf");
1370 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1371 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1373 check_verify_result (
1376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1377 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1378 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1379 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1380 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1385 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1387 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1388 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1392 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1394 path dir = "build/test/verify_missing_subtitle_language";
1395 prepare_directory (dir);
1396 auto dcp = make_simple (dir, 1, 106);
1399 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1400 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1401 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1402 "<ContentTitleText>Content</ContentTitleText>"
1403 "<AnnotationText>Annotation</AnnotationText>"
1404 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1405 "<ReelNumber>1</ReelNumber>"
1406 "<EditRate>24 1</EditRate>"
1407 "<TimeCodeRate>24</TimeCodeRate>"
1408 "<StartTime>00:00:00:00</StartTime>"
1409 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1411 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1412 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1413 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1419 dcp::File xml_file(dir / "subs.xml", "w");
1420 BOOST_REQUIRE (xml_file);
1421 xml_file.write(xml.c_str(), xml.size(), 1);
1423 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1424 subs->write (dir / "subs.mxf");
1426 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1427 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1430 check_verify_result (
1433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1434 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1439 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1441 path path ("build/test/verify_mismatched_subtitle_languages");
1442 auto constexpr reel_length = 192;
1443 auto dcp = make_simple (path, 2, reel_length);
1444 auto cpl = dcp->cpls()[0];
1447 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1448 subs->set_language (dcp::LanguageTag("de-DE"));
1449 subs->add (simple_subtitle());
1451 subs->write (path / "subs1.mxf");
1452 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1453 cpl->reels()[0]->add(reel_subs);
1457 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1458 subs->set_language (dcp::LanguageTag("en-US"));
1459 subs->add (simple_subtitle());
1461 subs->write (path / "subs2.mxf");
1462 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1463 cpl->reels()[1]->add(reel_subs);
1468 check_verify_result (
1471 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1472 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1473 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1478 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1480 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1481 auto constexpr reel_length = 192;
1482 auto dcp = make_simple (path, 2, reel_length);
1483 auto cpl = dcp->cpls()[0];
1486 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1487 ccaps->set_language (dcp::LanguageTag("de-DE"));
1488 ccaps->add (simple_subtitle());
1490 ccaps->write (path / "subs1.mxf");
1491 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1492 cpl->reels()[0]->add(reel_ccaps);
1496 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1497 ccaps->set_language (dcp::LanguageTag("en-US"));
1498 ccaps->add (simple_subtitle());
1500 ccaps->write (path / "subs2.mxf");
1501 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1502 cpl->reels()[1]->add(reel_ccaps);
1507 check_verify_result (
1510 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1511 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1516 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1518 path dir = "build/test/verify_missing_subtitle_start_time";
1519 prepare_directory (dir);
1520 auto dcp = make_simple (dir, 1, 106);
1523 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1524 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1525 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1526 "<ContentTitleText>Content</ContentTitleText>"
1527 "<AnnotationText>Annotation</AnnotationText>"
1528 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1529 "<ReelNumber>1</ReelNumber>"
1530 "<Language>de-DE</Language>"
1531 "<EditRate>24 1</EditRate>"
1532 "<TimeCodeRate>24</TimeCodeRate>"
1533 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1535 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1536 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1537 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1543 dcp::File xml_file(dir / "subs.xml", "w");
1544 BOOST_REQUIRE (xml_file);
1545 xml_file.write(xml.c_str(), xml.size(), 1);
1547 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1548 subs->write (dir / "subs.mxf");
1550 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1551 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1554 check_verify_result (
1557 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1558 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1563 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1565 path dir = "build/test/verify_invalid_subtitle_start_time";
1566 prepare_directory (dir);
1567 auto dcp = make_simple (dir, 1, 106);
1570 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1571 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1572 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1573 "<ContentTitleText>Content</ContentTitleText>"
1574 "<AnnotationText>Annotation</AnnotationText>"
1575 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1576 "<ReelNumber>1</ReelNumber>"
1577 "<Language>de-DE</Language>"
1578 "<EditRate>24 1</EditRate>"
1579 "<TimeCodeRate>24</TimeCodeRate>"
1580 "<StartTime>00:00:02:00</StartTime>"
1581 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1583 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1584 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1585 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1591 dcp::File xml_file(dir / "subs.xml", "w");
1592 BOOST_REQUIRE (xml_file);
1593 xml_file.write(xml.c_str(), xml.size(), 1);
1595 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1596 subs->write (dir / "subs.mxf");
1598 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1599 dcp->cpls().front()->reels().front()->add(reel_subs);
1602 check_verify_result (
1605 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1606 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1614 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1617 , v_position(v_position_)
1625 dcp::VAlign v_align;
1631 shared_ptr<dcp::CPL>
1632 dcp_with_text (path dir, vector<TestText> subs)
1634 prepare_directory (dir);
1635 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1636 asset->set_start_time (dcp::Time());
1637 for (auto i: subs) {
1638 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1640 asset->set_language (dcp::LanguageTag("de-DE"));
1642 asset->write (dir / "subs.mxf");
1644 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1645 return write_dcp_with_single_asset (dir, reel_asset);
1650 shared_ptr<dcp::CPL>
1651 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1653 prepare_directory (dir);
1654 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1655 asset->set_start_time (dcp::Time());
1656 asset->set_language (dcp::LanguageTag("de-DE"));
1658 auto subs_mxf = dir / "subs.mxf";
1659 asset->write (subs_mxf);
1661 /* The call to write() puts the asset into the DCP correctly but it will have
1662 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1665 ASDCP::TimedText::MXFWriter writer;
1666 ASDCP::WriterInfo writer_info;
1667 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1669 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1670 DCP_ASSERT (c == Kumu::UUID_Length);
1671 ASDCP::TimedText::TimedTextDescriptor descriptor;
1672 descriptor.ContainerDuration = asset->intrinsic_duration();
1673 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1674 DCP_ASSERT (c == Kumu::UUID_Length);
1675 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1676 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1677 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1678 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1681 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1682 return write_dcp_with_single_asset (dir, reel_asset);
1686 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1688 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1689 /* Just too early */
1690 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1691 check_verify_result (
1694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1695 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1701 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1703 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1704 /* Just late enough */
1705 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1706 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1710 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1712 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1713 prepare_directory (dir);
1715 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1716 asset1->set_start_time (dcp::Time());
1717 /* Just late enough */
1718 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1719 asset1->set_language (dcp::LanguageTag("de-DE"));
1721 asset1->write (dir / "subs1.mxf");
1722 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1723 auto reel1 = make_shared<dcp::Reel>();
1724 reel1->add (reel_asset1);
1725 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1726 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1727 reel1->add (markers1);
1729 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1730 asset2->set_start_time (dcp::Time());
1732 /* This would be too early on first reel but should be OK on the second */
1733 add_test_subtitle (asset2, 3, 4 * 24);
1734 asset2->set_language (dcp::LanguageTag("de-DE"));
1735 asset2->write (dir / "subs2.mxf");
1736 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1737 auto reel2 = make_shared<dcp::Reel>();
1738 reel2->add (reel_asset2);
1739 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1740 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1741 reel2->add (markers2);
1743 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1746 auto dcp = make_shared<dcp::DCP>(dir);
1748 dcp->set_annotation_text("hello");
1751 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1755 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1757 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1758 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1762 { 5 * 24 + 1, 6 * 24 },
1764 check_verify_result (
1767 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1768 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1773 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1775 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1776 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1780 { 5 * 24 + 16, 8 * 24 },
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_duration)
1788 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1789 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1790 check_verify_result (
1793 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1794 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1799 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1801 auto const dir = path("build/test/verify_valid_subtitle_duration");
1802 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1803 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1807 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1809 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1810 prepare_directory (dir);
1811 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1812 asset->set_start_time (dcp::Time());
1813 add_test_subtitle (asset, 0, 4 * 24);
1815 asset->set_language (dcp::LanguageTag("de-DE"));
1816 asset->write (dir / "subs.mxf");
1818 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1819 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1820 check_verify_result (
1823 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1824 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1825 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1826 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1832 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1834 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1835 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1838 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1839 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1840 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1841 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1843 check_verify_result (
1846 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1852 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1854 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1855 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1858 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1859 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1860 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1862 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1866 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1868 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1869 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1872 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1873 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1874 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1875 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1877 check_verify_result (
1880 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1881 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1886 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1888 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1889 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1892 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1893 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1894 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1895 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1897 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1901 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1903 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1904 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1907 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1909 check_verify_result (
1912 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1918 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1920 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1921 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1924 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1926 check_verify_result (
1929 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1935 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1937 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1938 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1941 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1942 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1943 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1944 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1946 check_verify_result (
1949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1950 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1955 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1957 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1958 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1961 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1962 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1963 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1965 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1969 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1971 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1972 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1975 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1976 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1977 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1978 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1980 check_verify_result (
1983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1989 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1991 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1992 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1995 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1996 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1997 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1998 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2000 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2004 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2006 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2007 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2010 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2012 check_verify_result (
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2020 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2022 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2023 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2026 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2028 check_verify_result (
2031 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2032 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2037 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2039 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2040 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2043 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2044 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2045 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2047 check_verify_result (
2050 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2055 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2057 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2058 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2061 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2062 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2063 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2074 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2076 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2077 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2080 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2081 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2082 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2084 check_verify_result (
2087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2092 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2094 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2095 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2098 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2099 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2100 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2102 check_verify_result (
2105 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2110 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2112 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2113 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2114 check_verify_result (
2117 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2123 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2125 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2126 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2127 check_verify_result (
2130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2136 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2138 path const dir("build/test/verify_invalid_sound_frame_rate");
2139 prepare_directory (dir);
2141 auto picture = simple_picture (dir, "foo");
2142 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2143 auto reel = make_shared<dcp::Reel>();
2144 reel->add (reel_picture);
2145 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2146 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2147 reel->add (reel_sound);
2148 reel->add (simple_markers());
2149 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2151 auto dcp = make_shared<dcp::DCP>(dir);
2153 dcp->set_annotation_text("hello");
2156 check_verify_result (
2159 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2160 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2165 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2167 path const dir("build/test/verify_missing_cpl_annotation_text");
2168 auto dcp = make_simple (dir);
2171 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2173 auto const cpl = dcp->cpls()[0];
2176 BOOST_REQUIRE (cpl->file());
2177 Editor e(cpl->file().get());
2178 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2181 check_verify_result (
2184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2185 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2190 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2192 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2193 auto dcp = make_simple (dir);
2196 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2197 auto const cpl = dcp->cpls()[0];
2200 BOOST_REQUIRE (cpl->file());
2201 Editor e(cpl->file().get());
2202 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2205 check_verify_result (
2208 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2209 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2214 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2216 path const dir("build/test/verify_mismatched_asset_duration");
2217 prepare_directory (dir);
2218 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2219 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2221 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2222 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2224 auto reel = make_shared<dcp::Reel>(
2225 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2226 make_shared<dcp::ReelSoundAsset>(ms, 0)
2229 reel->add (simple_markers());
2233 dcp->set_annotation_text("A Test DCP");
2236 check_verify_result (
2239 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2240 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2247 shared_ptr<dcp::CPL>
2248 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2250 prepare_directory (dir);
2251 auto dcp = make_shared<dcp::DCP>(dir);
2252 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2254 auto constexpr reel_length = 192;
2256 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2257 subs->set_language (dcp::LanguageTag("de-DE"));
2258 subs->set_start_time (dcp::Time());
2259 subs->add (simple_subtitle());
2261 subs->write (dir / "subs.mxf");
2262 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2264 auto reel1 = make_shared<dcp::Reel>(
2265 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2266 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2270 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2273 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2274 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2275 reel1->add (markers1);
2279 auto reel2 = make_shared<dcp::Reel>(
2280 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2281 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2285 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2288 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2289 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2290 reel2->add (markers2);
2295 dcp->set_annotation_text("A Test DCP");
2302 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2305 path dir ("build/test/missing_main_subtitle_from_some_reels");
2306 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2307 check_verify_result (
2310 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2311 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2317 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2318 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2319 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2323 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2324 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2325 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2331 shared_ptr<dcp::CPL>
2332 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2334 prepare_directory (dir);
2335 auto dcp = make_shared<dcp::DCP>(dir);
2336 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2338 auto constexpr reel_length = 192;
2340 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2341 subs->set_language (dcp::LanguageTag("de-DE"));
2342 subs->set_start_time (dcp::Time());
2343 subs->add (simple_subtitle());
2345 subs->write (dir / "subs.mxf");
2347 auto reel1 = make_shared<dcp::Reel>(
2348 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2349 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2352 for (int i = 0; i < caps_in_reel1; ++i) {
2353 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2356 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2357 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2358 reel1->add (markers1);
2362 auto reel2 = make_shared<dcp::Reel>(
2363 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2364 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2367 for (int i = 0; i < caps_in_reel2; ++i) {
2368 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2371 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2372 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2373 reel2->add (markers2);
2378 dcp->set_annotation_text("A Test DCP");
2385 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2388 path dir ("build/test/mismatched_closed_caption_asset_counts");
2389 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2390 check_verify_result (
2393 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2394 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2399 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2400 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2401 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2405 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2406 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2407 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2414 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2416 prepare_directory (dir);
2417 auto dcp = make_shared<dcp::DCP>(dir);
2418 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2420 auto constexpr reel_length = 192;
2422 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2423 subs->set_language (dcp::LanguageTag("de-DE"));
2424 subs->set_start_time (dcp::Time());
2425 subs->add (simple_subtitle());
2427 subs->write (dir / "subs.mxf");
2428 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2431 auto reel = make_shared<dcp::Reel>(
2432 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2433 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2436 reel->add (reel_text);
2438 reel->add (simple_markers(reel_length));
2443 dcp->set_annotation_text("A Test DCP");
2446 check_verify_result (
2449 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2450 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2455 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2457 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2458 "build/test/verify_subtitle_entry_point_must_be_present",
2459 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2460 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2461 asset->unset_entry_point ();
2465 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2466 "build/test/verify_subtitle_entry_point_must_be_zero",
2467 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2468 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2469 asset->set_entry_point (4);
2473 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2474 "build/test/verify_closed_caption_entry_point_must_be_present",
2475 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2476 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2477 asset->unset_entry_point ();
2481 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2482 "build/test/verify_closed_caption_entry_point_must_be_zero",
2483 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2484 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2485 asset->set_entry_point (9);
2491 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2495 path const dir("build/test/verify_missing_hash");
2496 auto dcp = make_simple (dir);
2499 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2500 auto const cpl = dcp->cpls()[0];
2501 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2502 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2503 auto asset_id = cpl->reels()[0]->main_picture()->id();
2506 BOOST_REQUIRE (cpl->file());
2507 Editor e(cpl->file().get());
2508 e.delete_first_line_containing("<Hash>");
2511 check_verify_result (
2514 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2515 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2522 verify_markers_test (
2524 vector<pair<dcp::Marker, dcp::Time>> markers,
2525 vector<dcp::VerificationNote> test_notes
2528 auto dcp = make_simple (dir);
2529 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2530 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2531 for (auto const& i: markers) {
2532 markers_asset->set (i.first, i.second);
2534 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2537 check_verify_result ({dir}, test_notes);
2541 BOOST_AUTO_TEST_CASE (verify_markers)
2543 verify_markers_test (
2544 "build/test/verify_markers_all_correct",
2546 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2547 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2548 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2549 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2554 verify_markers_test (
2555 "build/test/verify_markers_missing_ffec",
2557 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2558 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2559 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2562 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2565 verify_markers_test (
2566 "build/test/verify_markers_missing_ffmc",
2568 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2569 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2570 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2573 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2576 verify_markers_test (
2577 "build/test/verify_markers_missing_ffoc",
2579 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2580 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2581 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2584 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2587 verify_markers_test (
2588 "build/test/verify_markers_missing_lfoc",
2590 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2591 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2592 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2595 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2598 verify_markers_test (
2599 "build/test/verify_markers_incorrect_ffoc",
2601 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2602 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2603 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2604 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2607 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2610 verify_markers_test (
2611 "build/test/verify_markers_incorrect_lfoc",
2613 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2614 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2615 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2616 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2619 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2624 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2626 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2627 prepare_directory (dir);
2628 auto dcp = make_simple (dir);
2629 auto cpl = dcp->cpls()[0];
2630 cpl->unset_version_number();
2633 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2637 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2639 path dir = "build/test/verify_missing_extension_metadata1";
2640 auto dcp = make_simple (dir);
2643 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2644 auto cpl = dcp->cpls()[0];
2647 Editor e (cpl->file().get());
2648 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2651 check_verify_result (
2654 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2660 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2662 path dir = "build/test/verify_missing_extension_metadata2";
2663 auto dcp = make_simple (dir);
2666 auto cpl = dcp->cpls()[0];
2669 Editor e (cpl->file().get());
2670 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2673 check_verify_result (
2676 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2677 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2682 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2684 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2685 auto dcp = make_simple (dir);
2688 auto const cpl = dcp->cpls()[0];
2691 Editor e (cpl->file().get());
2692 e.replace ("<meta:Name>A", "<meta:NameX>A");
2693 e.replace ("n</meta:Name>", "n</meta:NameX>");
2696 check_verify_result (
2699 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2700 { 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 },
2701 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2706 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2708 path dir = "build/test/verify_invalid_extension_metadata1";
2709 auto dcp = make_simple (dir);
2712 auto cpl = dcp->cpls()[0];
2715 Editor e (cpl->file().get());
2716 e.replace ("Application", "Fred");
2719 check_verify_result (
2722 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2728 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2730 path dir = "build/test/verify_invalid_extension_metadata2";
2731 auto dcp = make_simple (dir);
2734 auto cpl = dcp->cpls()[0];
2737 Editor e (cpl->file().get());
2738 e.replace ("DCP Constraints Profile", "Fred");
2741 check_verify_result (
2744 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2745 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2750 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2752 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2753 auto dcp = make_simple (dir);
2756 auto const cpl = dcp->cpls()[0];
2759 Editor e (cpl->file().get());
2760 e.replace ("<meta:Value>", "<meta:ValueX>");
2761 e.replace ("</meta:Value>", "</meta:ValueX>");
2764 check_verify_result (
2767 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2768 { 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 },
2769 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2774 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2776 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2777 auto dcp = make_simple (dir);
2780 auto const cpl = dcp->cpls()[0];
2783 Editor e (cpl->file().get());
2784 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2787 check_verify_result (
2790 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2791 { 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() },
2796 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2798 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2799 auto dcp = make_simple (dir);
2802 auto const cpl = dcp->cpls()[0];
2805 Editor e (cpl->file().get());
2806 e.replace ("<meta:Property>", "<meta:PropertyX>");
2807 e.replace ("</meta:Property>", "</meta:PropertyX>");
2810 check_verify_result (
2813 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2814 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2815 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2820 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2822 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2823 auto dcp = make_simple (dir);
2826 auto const cpl = dcp->cpls()[0];
2829 Editor e (cpl->file().get());
2830 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2831 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2834 check_verify_result (
2837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2838 { 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 },
2839 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2845 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2847 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2848 prepare_directory (dir);
2849 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2850 copy_file (i.path(), dir / i.path().filename());
2853 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2854 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2858 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2861 check_verify_result (
2864 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2868 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2869 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2870 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2871 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2876 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2878 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2879 prepare_directory (dir);
2880 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2881 copy_file (i.path(), dir / i.path().filename());
2884 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2885 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2888 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2891 check_verify_result (
2894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2897 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2898 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2899 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2905 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2907 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2908 prepare_directory (dir);
2909 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2910 copy_file (i.path(), dir / i.path().filename());
2914 Editor e (dir / dcp_test1_pkl);
2915 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2918 check_verify_result ({dir}, {});
2922 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2924 path dir ("build/test/verify_must_not_be_partially_encrypted");
2925 prepare_directory (dir);
2929 auto signer = make_shared<dcp::CertificateChain>();
2930 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2931 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2932 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2933 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2935 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2939 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2942 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
2943 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2944 for (int i = 0; i < 24; ++i) {
2945 writer->write (j2c.data(), j2c.size());
2947 writer->finalize ();
2949 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2951 auto reel = make_shared<dcp::Reel>(
2952 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2953 make_shared<dcp::ReelSoundAsset>(ms, 0)
2956 reel->add (simple_markers());
2960 cpl->set_content_version (
2961 {"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"}
2963 cpl->set_annotation_text ("A Test DCP");
2964 cpl->set_issuer ("OpenDCP 0.0.25");
2965 cpl->set_creator ("OpenDCP 0.0.25");
2966 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2967 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
2968 cpl->set_main_sound_sample_rate (48000);
2969 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2970 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2971 cpl->set_version_number (1);
2975 d.set_issuer("OpenDCP 0.0.25");
2976 d.set_creator("OpenDCP 0.0.25");
2977 d.set_issue_date("2012-07-17T04:45:18+00:00");
2978 d.set_annotation_text("A Test DCP");
2979 d.write_xml(signer);
2981 check_verify_result (
2984 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2989 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2991 vector<dcp::VerificationNote> notes;
2992 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"));
2993 auto reader = picture.start_read ();
2994 auto frame = reader->get_frame (0);
2995 verify_j2k(frame, 0, 24, notes);
2996 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3000 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3002 vector<dcp::VerificationNote> notes;
3003 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3004 auto reader = picture.start_read ();
3005 auto frame = reader->get_frame (0);
3006 verify_j2k(frame, 0, 24, notes);
3007 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3011 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3013 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3014 prepare_directory (dir);
3015 auto dcp = make_simple (dir);
3017 vector<dcp::VerificationNote> notes;
3018 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3019 auto reader = picture.start_read ();
3020 auto frame = reader->get_frame (0);
3021 verify_j2k(frame, 0, 24, notes);
3022 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3026 /** Check that ResourceID and the XML ID being different is spotted */
3027 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3029 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3030 prepare_directory (dir);
3032 ASDCP::WriterInfo writer_info;
3033 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3036 auto mxf_id = dcp::make_uuid ();
3037 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3038 BOOST_REQUIRE (c == Kumu::UUID_Length);
3040 auto resource_id = dcp::make_uuid ();
3041 ASDCP::TimedText::TimedTextDescriptor descriptor;
3042 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3043 DCP_ASSERT (c == Kumu::UUID_Length);
3045 auto xml_id = dcp::make_uuid ();
3046 ASDCP::TimedText::MXFWriter writer;
3047 auto subs_mxf = dir / "subs.mxf";
3048 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3049 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3050 writer.WriteTimedTextResource (dcp::String::compose(
3051 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3052 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3053 "<Id>urn:uuid:%1</Id>"
3054 "<ContentTitleText>Content</ContentTitleText>"
3055 "<AnnotationText>Annotation</AnnotationText>"
3056 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3057 "<ReelNumber>1</ReelNumber>"
3058 "<Language>en-US</Language>"
3059 "<EditRate>25 1</EditRate>"
3060 "<TimeCodeRate>25</TimeCodeRate>"
3061 "<StartTime>00:00:00:00</StartTime>"
3062 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3064 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3065 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3066 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3075 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3076 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3078 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3080 check_verify_result (
3083 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3084 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3085 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3091 /** Check that ResourceID and the MXF ID being the same is spotted */
3092 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3094 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3095 prepare_directory (dir);
3097 ASDCP::WriterInfo writer_info;
3098 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3101 auto mxf_id = dcp::make_uuid ();
3102 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3103 BOOST_REQUIRE (c == Kumu::UUID_Length);
3105 auto resource_id = mxf_id;
3106 ASDCP::TimedText::TimedTextDescriptor descriptor;
3107 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3108 DCP_ASSERT (c == Kumu::UUID_Length);
3110 auto xml_id = resource_id;
3111 ASDCP::TimedText::MXFWriter writer;
3112 auto subs_mxf = dir / "subs.mxf";
3113 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3114 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3115 writer.WriteTimedTextResource (dcp::String::compose(
3116 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3117 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3118 "<Id>urn:uuid:%1</Id>"
3119 "<ContentTitleText>Content</ContentTitleText>"
3120 "<AnnotationText>Annotation</AnnotationText>"
3121 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3122 "<ReelNumber>1</ReelNumber>"
3123 "<Language>en-US</Language>"
3124 "<EditRate>25 1</EditRate>"
3125 "<TimeCodeRate>25</TimeCodeRate>"
3126 "<StartTime>00:00:00:00</StartTime>"
3127 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3129 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3130 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3131 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3140 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3141 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3143 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3145 check_verify_result (
3148 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3149 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3150 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3152 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3157 /** Check a DCP with a 3D asset marked as 2D */
3158 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3160 check_verify_result (
3161 { private_test / "data" / "xm" },
3164 dcp::VerificationNote::Type::WARNING,
3165 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3168 dcp::VerificationNote::Type::BV21_ERROR,
3169 dcp::VerificationNote::Code::INVALID_STANDARD
3176 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3178 path dir = "build/test/verify_unexpected_things_in_main_markers";
3179 prepare_directory (dir);
3180 auto dcp = make_simple (dir, 1, 24);
3184 Editor e (find_cpl(dir));
3186 " <IntrinsicDuration>24</IntrinsicDuration>",
3187 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3191 dcp::CPL cpl (find_cpl(dir));
3193 check_verify_result (
3196 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3197 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3198 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3203 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3205 path dir = "build/test/verify_invalid_content_kind";
3206 prepare_directory (dir);
3207 auto dcp = make_simple (dir, 1, 24);
3211 Editor e(find_cpl(dir));
3212 e.replace("trailer", "trip");
3215 dcp::CPL cpl (find_cpl(dir));
3217 check_verify_result (
3220 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3221 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3227 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3229 path dir = "build/test/verify_valid_content_kind";
3230 prepare_directory (dir);
3231 auto dcp = make_simple (dir, 1, 24);
3235 Editor e(find_cpl(dir));
3236 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3239 dcp::CPL cpl (find_cpl(dir));
3241 check_verify_result (
3244 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3250 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3252 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3253 prepare_directory(dir);
3254 auto dcp = make_simple(dir, 1, 24);
3257 auto constexpr area = "<meta:MainPictureActiveArea>";
3260 Editor e(find_cpl(dir));
3261 e.delete_lines_after(area, 2);
3262 e.insert(area, "<meta:Height>4080</meta:Height>");
3263 e.insert(area, "<meta:Width>1997</meta:Width>");
3266 dcp::PKL pkl(find_pkl(dir));
3267 dcp::CPL cpl(find_cpl(dir));
3269 check_verify_result(
3272 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3273 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3274 { 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)) },
3279 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3281 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3282 prepare_directory(dir);
3283 auto dcp = make_simple(dir, 1, 24);
3286 auto constexpr area = "<meta:MainPictureActiveArea>";
3289 Editor e(find_cpl(dir));
3290 e.delete_lines_after(area, 2);
3291 e.insert(area, "<meta:Height>5125</meta:Height>");
3292 e.insert(area, "<meta:Width>9900</meta:Width>");
3295 dcp::PKL pkl(find_pkl(dir));
3296 dcp::CPL cpl(find_cpl(dir));
3298 check_verify_result(
3301 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3302 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3303 { 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)) },
3304 { 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)) },
3309 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3313 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3314 prepare_directory(dir);
3315 auto dcp = make_simple(dir, 1, 24);
3319 Editor e(find_pkl(dir));
3320 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3323 dcp::PKL pkl(find_pkl(dir));
3325 check_verify_result(
3328 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3333 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3337 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3338 prepare_directory(dir);
3339 auto dcp = make_simple(dir, 1, 24);
3343 Editor e(find_asset_map(dir));
3344 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3347 dcp::PKL pkl(find_pkl(dir));
3348 dcp::AssetMap asset_map(find_asset_map(dir));
3350 check_verify_result(
3353 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3354 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3359 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3361 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3363 dcp::MXFMetadata mxf_meta;
3364 mxf_meta.company_name = "OpenDCP";
3365 mxf_meta.product_name = "OpenDCP";
3366 mxf_meta.product_version = "0.0.25";
3368 auto constexpr sample_rate = 48000;
3369 auto constexpr frames = 240;
3371 boost::filesystem::remove_all(path);
3372 boost::filesystem::create_directories(path);
3373 auto dcp = make_shared<dcp::DCP>(path);
3374 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3375 cpl->set_annotation_text("hello");
3376 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3377 cpl->set_main_sound_sample_rate(sample_rate);
3378 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3379 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3380 cpl->set_version_number(1);
3384 /* Reel with 2 channels of audio */
3386 auto mp = simple_picture(path, "1", frames, {});
3387 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3389 auto reel = make_shared<dcp::Reel>(
3390 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3391 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3394 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3395 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3402 /* Reel with 6 channels of audio */
3404 auto mp = simple_picture(path, "2", frames, {});
3405 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3407 auto reel = make_shared<dcp::Reel>(
3408 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3409 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3412 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3413 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3420 dcp->set_annotation_text("hello");
3423 check_verify_result(
3426 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3431 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3433 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3435 dcp::MXFMetadata mxf_meta;
3436 mxf_meta.company_name = "OpenDCP";
3437 mxf_meta.product_name = "OpenDCP";
3438 mxf_meta.product_version = "0.0.25";
3440 auto constexpr sample_rate = 48000;
3441 auto constexpr frames = 240;
3443 boost::filesystem::remove_all(path);
3444 boost::filesystem::create_directories(path);
3445 auto dcp = make_shared<dcp::DCP>(path);
3446 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3447 cpl->set_annotation_text("hello");
3448 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3449 cpl->set_main_sound_sample_rate(sample_rate);
3450 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3451 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3452 cpl->set_version_number(1);
3454 auto mp = simple_picture(path, "1", frames, {});
3455 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3457 auto reel = make_shared<dcp::Reel>(
3458 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3459 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3462 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3463 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3464 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3470 dcp->set_annotation_text("hello");
3473 check_verify_result(
3476 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, std::string{"MainSoundConfiguration has 6 channels but sound assets have 2"}, canonical(find_cpl(path)) },
3481 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3483 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3484 auto constexpr video_frames = 24;
3485 auto constexpr sample_rate = 48000;
3487 boost::filesystem::remove_all(path);
3488 boost::filesystem::create_directories(path);
3490 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3491 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3493 dcp::Size const size(1998, 1080);
3494 auto image = make_shared<dcp::OpenJPEGImage>(size);
3495 boost::random::mt19937 rng(1);
3496 boost::random::uniform_int_distribution<> dist(0, 4095);
3497 for (int c = 0; c < 3; ++c) {
3498 for (int p = 0; p < (1998 * 1080); ++p) {
3499 image->data(c)[p] = dist(rng);
3502 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3503 for (int i = 0; i < 24; ++i) {
3504 picture_writer->write(j2c.data(), j2c.size());
3506 picture_writer->finalize();
3508 auto dcp = make_shared<dcp::DCP>(path);
3509 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3510 cpl->set_content_version(
3511 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3513 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3514 cpl->set_main_sound_sample_rate(sample_rate);
3515 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3516 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3517 cpl->set_version_number(1);
3519 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3521 auto reel = make_shared<dcp::Reel>(
3522 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3523 make_shared<dcp::ReelSoundAsset>(ms, 0)
3528 dcp->set_annotation_text("A Test DCP");
3531 check_verify_result(
3534 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3535 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3536 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3537 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3542 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3544 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3545 check_verify_result(
3548 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3549 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3550 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3552 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3553 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3558 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3560 path const dir("build/test/verify_missing_load_font");
3561 prepare_directory (dir);
3562 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3564 Editor editor(dir / "subs.xml");
3565 editor.delete_first_line_containing("LoadFont");
3567 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3568 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3569 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3571 check_verify_result (
3573 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3574 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3580 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3582 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3583 prepare_directory(dir);
3584 auto dcp = make_simple (dir, 1, 202);
3587 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3588 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3589 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3590 "<ContentTitleText>Content</ContentTitleText>"
3591 "<AnnotationText>Annotation</AnnotationText>"
3592 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3593 "<ReelNumber>1</ReelNumber>"
3594 "<EditRate>24 1</EditRate>"
3595 "<TimeCodeRate>24</TimeCodeRate>"
3596 "<StartTime>00:00:00:00</StartTime>"
3597 "<Language>de-DE</Language>"
3599 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3600 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3601 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3607 dcp::File xml_file(dir / "subs.xml", "w");
3608 BOOST_REQUIRE(xml_file);
3609 xml_file.write(xml.c_str(), xml.size(), 1);
3611 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3612 subs->write(dir / "subs.mxf");
3614 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3615 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3618 check_verify_result (
3621 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3626 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3628 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3629 boost::filesystem::remove_all(dir);
3631 auto dcp1 = make_simple(dir / "1");
3634 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3636 auto dcp2 = make_simple(dir / "2");
3638 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3640 boost::filesystem::remove(dir / "1" / "video.mxf");
3641 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3643 check_verify_result(
3646 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3651 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3653 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3654 boost::filesystem::remove_all(dir);
3656 auto dcp = make_simple(dir);
3657 BOOST_REQUIRE(dcp->cpls().size() == 1);
3658 auto cpl = dcp->cpls()[0];
3659 cpl->set_content_version(dcp::ContentVersion(""));
3662 check_verify_result(
3665 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())