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_invalid_interop_subtitles)
675 using namespace boost::filesystem;
677 path const dir("build/test/verify_invalid_interop_subtitles");
678 prepare_directory (dir);
679 copy_file ("test/data/subs1.xml", dir / "subs.xml");
680 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
681 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
682 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
685 Editor e (dir / "subs.xml");
686 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
689 check_verify_result (
692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
693 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
695 dcp::VerificationNote::Type::ERROR,
696 dcp::VerificationNote::Code::INVALID_XML,
697 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
701 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
706 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
708 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
709 prepare_directory(dir);
710 copy_file("test/data/subs4.xml", dir / "subs.xml");
711 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
712 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
713 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
715 check_verify_result (
718 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
719 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
720 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
726 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
728 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
729 prepare_directory(dir);
730 copy_file("test/data/subs5.xml", dir / "subs.xml");
731 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
732 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
733 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
735 check_verify_result (
738 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
739 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
745 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
747 path const dir("build/test/verify_valid_smpte_subtitles");
748 prepare_directory (dir);
749 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
750 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
751 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
752 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
758 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
759 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
764 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
766 using namespace boost::filesystem;
768 path const dir("build/test/verify_invalid_smpte_subtitles");
769 prepare_directory (dir);
770 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
771 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
772 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
773 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
774 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
776 check_verify_result (
779 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
781 dcp::VerificationNote::Type::ERROR,
782 dcp::VerificationNote::Code::INVALID_XML,
783 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
787 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
788 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
789 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
790 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
795 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
797 path const dir("build/test/verify_empty_text_node_in_subtitles");
798 prepare_directory (dir);
799 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
800 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
801 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
802 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
804 check_verify_result (
807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
808 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
810 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
811 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
812 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
817 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
818 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
820 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
821 prepare_directory (dir);
822 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
823 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
824 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
825 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
827 check_verify_result (
830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
831 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
836 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
837 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
839 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
840 prepare_directory (dir);
841 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
842 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
843 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
844 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
846 check_verify_result (
849 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
851 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
852 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
857 BOOST_AUTO_TEST_CASE (verify_external_asset)
859 path const ov_dir("build/test/verify_external_asset");
860 prepare_directory (ov_dir);
862 auto image = black_image ();
863 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
864 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
865 dcp_from_frame (frame, ov_dir);
867 dcp::DCP ov (ov_dir);
870 path const vf_dir("build/test/verify_external_asset_vf");
871 prepare_directory (vf_dir);
873 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
874 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
876 check_verify_result (
879 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
880 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
885 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
887 path const dir("build/test/verify_valid_cpl_metadata");
888 prepare_directory (dir);
890 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
891 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
892 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
894 auto reel = make_shared<dcp::Reel>();
895 reel->add (reel_asset);
897 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
898 reel->add (simple_markers(16 * 24));
900 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
902 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
903 cpl->set_main_sound_sample_rate (48000);
904 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
905 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
906 cpl->set_version_number (1);
910 dcp.set_annotation_text("hello");
916 find_prefix(path dir, string prefix)
918 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
919 return boost::starts_with(p.filename().string(), prefix);
922 BOOST_REQUIRE(iter != directory_iterator());
927 path find_cpl (path dir)
929 return find_prefix(dir, "cpl_");
936 return find_prefix(dir, "pkl_");
941 find_asset_map(path dir)
943 return find_prefix(dir, "ASSETMAP");
947 /* DCP with invalid CompositionMetadataAsset */
948 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
950 using namespace boost::filesystem;
952 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
953 prepare_directory (dir);
955 auto reel = make_shared<dcp::Reel>();
956 reel->add (black_picture_asset(dir));
957 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
959 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
960 cpl->set_main_sound_sample_rate (48000);
961 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
962 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
963 cpl->set_version_number (1);
965 reel->add (simple_markers());
969 dcp.set_annotation_text("hello");
973 Editor e (find_cpl(dir));
974 e.replace ("MainSound", "MainSoundX");
977 check_verify_result (
980 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
981 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
983 dcp::VerificationNote::Type::ERROR,
984 dcp::VerificationNote::Code::INVALID_XML,
985 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
986 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
987 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
988 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
989 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
990 "ExtensionMetadataList?,)'"),
991 canonical(cpl->file().get()),
994 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
999 /* DCP with invalid CompositionMetadataAsset */
1000 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1002 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1003 prepare_directory (dir);
1005 auto reel = make_shared<dcp::Reel>();
1006 reel->add (black_picture_asset(dir));
1007 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1009 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1010 cpl->set_main_sound_sample_rate (48000);
1011 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1012 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1016 dcp.set_annotation_text("hello");
1020 Editor e (find_cpl(dir));
1021 e.replace ("meta:Width", "meta:WidthX");
1024 check_verify_result (
1026 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1031 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1033 path const dir("build/test/verify_invalid_language1");
1034 prepare_directory (dir);
1035 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1036 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1037 asset->_language = "wrong-andbad";
1038 asset->write (dir / "subs.mxf");
1039 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1040 reel_asset->_language = "badlang";
1041 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1043 check_verify_result (
1046 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1047 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1048 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1053 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1054 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1056 path const dir("build/test/verify_invalid_language2");
1057 prepare_directory (dir);
1058 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1059 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1060 asset->_language = "wrong-andbad";
1061 asset->write (dir / "subs.mxf");
1062 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1063 reel_asset->_language = "badlang";
1064 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1066 check_verify_result (
1069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1070 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1071 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1076 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1077 * the release territory.
1079 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1081 path const dir("build/test/verify_invalid_language3");
1082 prepare_directory (dir);
1084 auto picture = simple_picture (dir, "foo");
1085 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1086 auto reel = make_shared<dcp::Reel>();
1087 reel->add (reel_picture);
1088 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1089 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1090 reel->add (reel_sound);
1091 reel->add (simple_markers());
1093 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1095 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1096 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1097 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1098 cpl->set_main_sound_sample_rate (48000);
1099 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1100 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1101 cpl->set_version_number (1);
1102 cpl->_release_territory = "fred-jim";
1103 auto dcp = make_shared<dcp::DCP>(dir);
1105 dcp->set_annotation_text("hello");
1108 check_verify_result (
1111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1112 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1120 vector<dcp::VerificationNote>
1121 check_picture_size (int width, int height, int frame_rate, bool three_d)
1123 using namespace boost::filesystem;
1125 path dcp_path = "build/test/verify_picture_test";
1126 prepare_directory (dcp_path);
1128 shared_ptr<dcp::PictureAsset> mp;
1130 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1132 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1134 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1136 auto image = black_image (dcp::Size(width, height));
1137 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1138 int const length = three_d ? frame_rate * 2 : frame_rate;
1139 for (int i = 0; i < length; ++i) {
1140 picture_writer->write (j2c.data(), j2c.size());
1142 picture_writer->finalize ();
1144 auto d = make_shared<dcp::DCP>(dcp_path);
1145 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1146 cpl->set_annotation_text ("A Test DCP");
1147 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1148 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1149 cpl->set_main_sound_sample_rate (48000);
1150 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1151 cpl->set_main_picture_active_area(dcp::Size(width, height));
1152 cpl->set_version_number (1);
1154 auto reel = make_shared<dcp::Reel>();
1157 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1159 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1162 reel->add (simple_markers(frame_rate));
1167 d->set_annotation_text("A Test DCP");
1170 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1176 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1178 auto notes = check_picture_size(width, height, frame_rate, three_d);
1179 BOOST_CHECK_EQUAL (notes.size(), 0U);
1185 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1187 auto notes = check_picture_size(width, height, frame_rate, three_d);
1188 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1189 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1190 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1196 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1198 auto notes = check_picture_size(width, height, frame_rate, three_d);
1199 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1200 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1201 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1207 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1209 auto notes = check_picture_size(width, height, frame_rate, three_d);
1210 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1211 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1212 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1216 BOOST_AUTO_TEST_CASE (verify_picture_size)
1218 using namespace boost::filesystem;
1221 check_picture_size_ok (2048, 858, 24, false);
1222 check_picture_size_ok (2048, 858, 25, false);
1223 check_picture_size_ok (2048, 858, 48, false);
1224 check_picture_size_ok (2048, 858, 24, true);
1225 check_picture_size_ok (2048, 858, 25, true);
1226 check_picture_size_ok (2048, 858, 48, true);
1229 check_picture_size_ok (1998, 1080, 24, false);
1230 check_picture_size_ok (1998, 1080, 25, false);
1231 check_picture_size_ok (1998, 1080, 48, false);
1232 check_picture_size_ok (1998, 1080, 24, true);
1233 check_picture_size_ok (1998, 1080, 25, true);
1234 check_picture_size_ok (1998, 1080, 48, true);
1237 check_picture_size_ok (4096, 1716, 24, false);
1240 check_picture_size_ok (3996, 2160, 24, false);
1242 /* Bad frame size */
1243 check_picture_size_bad_frame_size (2050, 858, 24, false);
1244 check_picture_size_bad_frame_size (2048, 658, 25, false);
1245 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1246 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1248 /* Bad 2K frame rate */
1249 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1250 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1251 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1253 /* Bad 4K frame rate */
1254 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1255 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1258 auto notes = check_picture_size(3996, 2160, 24, true);
1259 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1260 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1261 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1267 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")
1270 std::make_shared<dcp::SubtitleString>(
1278 dcp::Time(start_frame, 24, 24),
1279 dcp::Time(end_frame, 24, 24),
1281 dcp::HAlign::CENTER,
1285 dcp::Direction::LTR,
1297 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1299 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1300 prepare_directory (dir);
1302 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1303 for (int i = 0; i < 2048; ++i) {
1304 add_test_subtitle (asset, i * 24, i * 24 + 20);
1307 asset->set_language (dcp::LanguageTag("de-DE"));
1308 asset->write (dir / "subs.mxf");
1309 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1310 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1312 check_verify_result (
1315 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1317 dcp::VerificationNote::Type::BV21_ERROR,
1318 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1320 canonical(dir / "subs.mxf")
1322 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1323 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1329 shared_ptr<dcp::SMPTESubtitleAsset>
1330 make_large_subtitle_asset (path font_file)
1332 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1333 dcp::ArrayData big_fake_font(1024 * 1024);
1334 big_fake_font.write (font_file);
1335 for (int i = 0; i < 116; ++i) {
1336 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1344 verify_timed_text_asset_too_large (string name)
1346 auto const dir = path("build/test") / name;
1347 prepare_directory (dir);
1348 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1349 add_test_subtitle (asset, 0, 240);
1350 asset->set_language (dcp::LanguageTag("de-DE"));
1351 asset->write (dir / "subs.mxf");
1353 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1354 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1356 check_verify_result (
1359 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1360 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1361 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1362 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1363 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1368 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1370 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1371 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1375 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1377 path dir = "build/test/verify_missing_subtitle_language";
1378 prepare_directory (dir);
1379 auto dcp = make_simple (dir, 1, 106);
1382 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1383 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1384 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1385 "<ContentTitleText>Content</ContentTitleText>"
1386 "<AnnotationText>Annotation</AnnotationText>"
1387 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1388 "<ReelNumber>1</ReelNumber>"
1389 "<EditRate>24 1</EditRate>"
1390 "<TimeCodeRate>24</TimeCodeRate>"
1391 "<StartTime>00:00:00:00</StartTime>"
1392 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1394 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1395 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1396 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1402 dcp::File xml_file(dir / "subs.xml", "w");
1403 BOOST_REQUIRE (xml_file);
1404 xml_file.write(xml.c_str(), xml.size(), 1);
1406 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1407 subs->write (dir / "subs.mxf");
1409 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1410 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1413 check_verify_result (
1416 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1417 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1422 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1424 path path ("build/test/verify_mismatched_subtitle_languages");
1425 auto constexpr reel_length = 192;
1426 auto dcp = make_simple (path, 2, reel_length);
1427 auto cpl = dcp->cpls()[0];
1430 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1431 subs->set_language (dcp::LanguageTag("de-DE"));
1432 subs->add (simple_subtitle());
1434 subs->write (path / "subs1.mxf");
1435 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1436 cpl->reels()[0]->add(reel_subs);
1440 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1441 subs->set_language (dcp::LanguageTag("en-US"));
1442 subs->add (simple_subtitle());
1444 subs->write (path / "subs2.mxf");
1445 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1446 cpl->reels()[1]->add(reel_subs);
1451 check_verify_result (
1454 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1455 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1456 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1461 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1463 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1464 auto constexpr reel_length = 192;
1465 auto dcp = make_simple (path, 2, reel_length);
1466 auto cpl = dcp->cpls()[0];
1469 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1470 ccaps->set_language (dcp::LanguageTag("de-DE"));
1471 ccaps->add (simple_subtitle());
1473 ccaps->write (path / "subs1.mxf");
1474 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1475 cpl->reels()[0]->add(reel_ccaps);
1479 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1480 ccaps->set_language (dcp::LanguageTag("en-US"));
1481 ccaps->add (simple_subtitle());
1483 ccaps->write (path / "subs2.mxf");
1484 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1485 cpl->reels()[1]->add(reel_ccaps);
1490 check_verify_result (
1493 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1494 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1499 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1501 path dir = "build/test/verify_missing_subtitle_start_time";
1502 prepare_directory (dir);
1503 auto dcp = make_simple (dir, 1, 106);
1506 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1507 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1508 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1509 "<ContentTitleText>Content</ContentTitleText>"
1510 "<AnnotationText>Annotation</AnnotationText>"
1511 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1512 "<ReelNumber>1</ReelNumber>"
1513 "<Language>de-DE</Language>"
1514 "<EditRate>24 1</EditRate>"
1515 "<TimeCodeRate>24</TimeCodeRate>"
1516 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1518 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1519 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1520 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1526 dcp::File xml_file(dir / "subs.xml", "w");
1527 BOOST_REQUIRE (xml_file);
1528 xml_file.write(xml.c_str(), xml.size(), 1);
1530 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1531 subs->write (dir / "subs.mxf");
1533 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1534 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1537 check_verify_result (
1540 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1541 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1546 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1548 path dir = "build/test/verify_invalid_subtitle_start_time";
1549 prepare_directory (dir);
1550 auto dcp = make_simple (dir, 1, 106);
1553 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1554 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1555 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1556 "<ContentTitleText>Content</ContentTitleText>"
1557 "<AnnotationText>Annotation</AnnotationText>"
1558 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1559 "<ReelNumber>1</ReelNumber>"
1560 "<Language>de-DE</Language>"
1561 "<EditRate>24 1</EditRate>"
1562 "<TimeCodeRate>24</TimeCodeRate>"
1563 "<StartTime>00:00:02:00</StartTime>"
1564 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1566 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1567 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1568 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1574 dcp::File xml_file(dir / "subs.xml", "w");
1575 BOOST_REQUIRE (xml_file);
1576 xml_file.write(xml.c_str(), xml.size(), 1);
1578 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1579 subs->write (dir / "subs.mxf");
1581 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1582 dcp->cpls().front()->reels().front()->add(reel_subs);
1585 check_verify_result (
1588 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1589 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1597 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1600 , v_position(v_position_)
1608 dcp::VAlign v_align;
1614 shared_ptr<dcp::CPL>
1615 dcp_with_text (path dir, vector<TestText> subs)
1617 prepare_directory (dir);
1618 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1619 asset->set_start_time (dcp::Time());
1620 for (auto i: subs) {
1621 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1623 asset->set_language (dcp::LanguageTag("de-DE"));
1625 asset->write (dir / "subs.mxf");
1627 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1628 return write_dcp_with_single_asset (dir, reel_asset);
1633 shared_ptr<dcp::CPL>
1634 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1636 prepare_directory (dir);
1637 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1638 asset->set_start_time (dcp::Time());
1639 asset->set_language (dcp::LanguageTag("de-DE"));
1641 auto subs_mxf = dir / "subs.mxf";
1642 asset->write (subs_mxf);
1644 /* The call to write() puts the asset into the DCP correctly but it will have
1645 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1648 ASDCP::TimedText::MXFWriter writer;
1649 ASDCP::WriterInfo writer_info;
1650 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1652 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1653 DCP_ASSERT (c == Kumu::UUID_Length);
1654 ASDCP::TimedText::TimedTextDescriptor descriptor;
1655 descriptor.ContainerDuration = asset->intrinsic_duration();
1656 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1657 DCP_ASSERT (c == Kumu::UUID_Length);
1658 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1659 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1660 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1661 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1664 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1665 return write_dcp_with_single_asset (dir, reel_asset);
1669 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1671 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1672 /* Just too early */
1673 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1674 check_verify_result (
1677 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1678 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1684 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1686 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1687 /* Just late enough */
1688 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1689 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1693 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1695 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1696 prepare_directory (dir);
1698 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1699 asset1->set_start_time (dcp::Time());
1700 /* Just late enough */
1701 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1702 asset1->set_language (dcp::LanguageTag("de-DE"));
1704 asset1->write (dir / "subs1.mxf");
1705 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1706 auto reel1 = make_shared<dcp::Reel>();
1707 reel1->add (reel_asset1);
1708 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1709 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1710 reel1->add (markers1);
1712 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1713 asset2->set_start_time (dcp::Time());
1715 /* This would be too early on first reel but should be OK on the second */
1716 add_test_subtitle (asset2, 3, 4 * 24);
1717 asset2->set_language (dcp::LanguageTag("de-DE"));
1718 asset2->write (dir / "subs2.mxf");
1719 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1720 auto reel2 = make_shared<dcp::Reel>();
1721 reel2->add (reel_asset2);
1722 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1723 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1724 reel2->add (markers2);
1726 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1729 auto dcp = make_shared<dcp::DCP>(dir);
1731 dcp->set_annotation_text("hello");
1734 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1738 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1740 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1741 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1745 { 5 * 24 + 1, 6 * 24 },
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1756 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1758 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1759 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1763 { 5 * 24 + 16, 8 * 24 },
1765 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1769 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1771 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1772 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1773 check_verify_result (
1776 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1782 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1784 auto const dir = path("build/test/verify_valid_subtitle_duration");
1785 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1786 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1790 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1792 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1793 prepare_directory (dir);
1794 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1795 asset->set_start_time (dcp::Time());
1796 add_test_subtitle (asset, 0, 4 * 24);
1798 asset->set_language (dcp::LanguageTag("de-DE"));
1799 asset->write (dir / "subs.mxf");
1801 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1802 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1803 check_verify_result (
1806 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1808 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1815 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1817 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1818 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1821 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1822 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1823 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1824 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1826 check_verify_result (
1829 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1835 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1837 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1838 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1841 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1842 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1843 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1845 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1849 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1851 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1852 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1855 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1856 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1857 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1858 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1860 check_verify_result (
1863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1869 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1871 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1872 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1875 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1876 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1877 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1878 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1880 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1884 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1886 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1887 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1890 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1892 check_verify_result (
1895 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1896 { 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_length2)
1903 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1904 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1907 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1909 check_verify_result (
1912 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::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_valid_closed_caption_line_count1)
1920 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1921 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1924 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1925 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1926 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1927 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1929 check_verify_result (
1932 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1933 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1938 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1940 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1941 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1944 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1945 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1946 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1948 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1952 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1954 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1955 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1958 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1959 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1960 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1961 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1963 check_verify_result (
1966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1967 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1972 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1974 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1975 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1978 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1979 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1980 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1981 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1983 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1987 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1989 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1990 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1993 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1995 check_verify_result (
1998 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2003 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2005 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2006 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2009 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2011 check_verify_result (
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2020 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2022 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2023 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2026 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2027 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2028 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2030 check_verify_result (
2033 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2038 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2040 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2041 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2044 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2045 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2046 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2048 check_verify_result (
2051 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2052 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2057 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2059 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2060 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2063 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2064 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2065 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2067 check_verify_result (
2070 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2075 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2077 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2078 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2081 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2082 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2083 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2085 check_verify_result (
2088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2093 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2095 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2096 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2097 check_verify_result (
2100 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2101 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2106 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2108 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2109 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2110 check_verify_result (
2113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2119 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2121 path const dir("build/test/verify_invalid_sound_frame_rate");
2122 prepare_directory (dir);
2124 auto picture = simple_picture (dir, "foo");
2125 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2126 auto reel = make_shared<dcp::Reel>();
2127 reel->add (reel_picture);
2128 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2129 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2130 reel->add (reel_sound);
2131 reel->add (simple_markers());
2132 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2134 auto dcp = make_shared<dcp::DCP>(dir);
2136 dcp->set_annotation_text("hello");
2139 check_verify_result (
2142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2143 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2148 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2150 path const dir("build/test/verify_missing_cpl_annotation_text");
2151 auto dcp = make_simple (dir);
2154 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2156 auto const cpl = dcp->cpls()[0];
2159 BOOST_REQUIRE (cpl->file());
2160 Editor e(cpl->file().get());
2161 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2164 check_verify_result (
2167 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2168 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2173 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2175 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2176 auto dcp = make_simple (dir);
2179 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2180 auto const cpl = dcp->cpls()[0];
2183 BOOST_REQUIRE (cpl->file());
2184 Editor e(cpl->file().get());
2185 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2188 check_verify_result (
2191 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2192 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2197 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2199 path const dir("build/test/verify_mismatched_asset_duration");
2200 prepare_directory (dir);
2201 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2202 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2204 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2205 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2207 auto reel = make_shared<dcp::Reel>(
2208 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2209 make_shared<dcp::ReelSoundAsset>(ms, 0)
2212 reel->add (simple_markers());
2216 dcp->set_annotation_text("A Test DCP");
2219 check_verify_result (
2222 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2223 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2230 shared_ptr<dcp::CPL>
2231 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2233 prepare_directory (dir);
2234 auto dcp = make_shared<dcp::DCP>(dir);
2235 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2237 auto constexpr reel_length = 192;
2239 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2240 subs->set_language (dcp::LanguageTag("de-DE"));
2241 subs->set_start_time (dcp::Time());
2242 subs->add (simple_subtitle());
2244 subs->write (dir / "subs.mxf");
2245 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2247 auto reel1 = make_shared<dcp::Reel>(
2248 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2249 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2253 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2256 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2257 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2258 reel1->add (markers1);
2262 auto reel2 = make_shared<dcp::Reel>(
2263 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2264 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2268 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2271 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2272 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2273 reel2->add (markers2);
2278 dcp->set_annotation_text("A Test DCP");
2285 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2288 path dir ("build/test/missing_main_subtitle_from_some_reels");
2289 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2290 check_verify_result (
2293 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2294 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2300 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2301 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2302 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2306 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2307 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2308 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2314 shared_ptr<dcp::CPL>
2315 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2317 prepare_directory (dir);
2318 auto dcp = make_shared<dcp::DCP>(dir);
2319 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2321 auto constexpr reel_length = 192;
2323 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2324 subs->set_language (dcp::LanguageTag("de-DE"));
2325 subs->set_start_time (dcp::Time());
2326 subs->add (simple_subtitle());
2328 subs->write (dir / "subs.mxf");
2330 auto reel1 = make_shared<dcp::Reel>(
2331 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2332 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2335 for (int i = 0; i < caps_in_reel1; ++i) {
2336 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2339 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2340 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2341 reel1->add (markers1);
2345 auto reel2 = make_shared<dcp::Reel>(
2346 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2347 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2350 for (int i = 0; i < caps_in_reel2; ++i) {
2351 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2354 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2355 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2356 reel2->add (markers2);
2361 dcp->set_annotation_text("A Test DCP");
2368 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2371 path dir ("build/test/mismatched_closed_caption_asset_counts");
2372 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2373 check_verify_result (
2376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2377 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2382 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2383 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2384 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2388 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2389 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2390 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2397 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2399 prepare_directory (dir);
2400 auto dcp = make_shared<dcp::DCP>(dir);
2401 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2403 auto constexpr reel_length = 192;
2405 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2406 subs->set_language (dcp::LanguageTag("de-DE"));
2407 subs->set_start_time (dcp::Time());
2408 subs->add (simple_subtitle());
2410 subs->write (dir / "subs.mxf");
2411 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2414 auto reel = make_shared<dcp::Reel>(
2415 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2416 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2419 reel->add (reel_text);
2421 reel->add (simple_markers(reel_length));
2426 dcp->set_annotation_text("A Test DCP");
2429 check_verify_result (
2432 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2438 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2440 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2441 "build/test/verify_subtitle_entry_point_must_be_present",
2442 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2443 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2444 asset->unset_entry_point ();
2448 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2449 "build/test/verify_subtitle_entry_point_must_be_zero",
2450 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2451 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2452 asset->set_entry_point (4);
2456 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2457 "build/test/verify_closed_caption_entry_point_must_be_present",
2458 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2459 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2460 asset->unset_entry_point ();
2464 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2465 "build/test/verify_closed_caption_entry_point_must_be_zero",
2466 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2467 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2468 asset->set_entry_point (9);
2474 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2478 path const dir("build/test/verify_missing_hash");
2479 auto dcp = make_simple (dir);
2482 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2483 auto const cpl = dcp->cpls()[0];
2484 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2485 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2486 auto asset_id = cpl->reels()[0]->main_picture()->id();
2489 BOOST_REQUIRE (cpl->file());
2490 Editor e(cpl->file().get());
2491 e.delete_first_line_containing("<Hash>");
2494 check_verify_result (
2497 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2498 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2505 verify_markers_test (
2507 vector<pair<dcp::Marker, dcp::Time>> markers,
2508 vector<dcp::VerificationNote> test_notes
2511 auto dcp = make_simple (dir);
2512 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2513 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2514 for (auto const& i: markers) {
2515 markers_asset->set (i.first, i.second);
2517 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2520 check_verify_result ({dir}, test_notes);
2524 BOOST_AUTO_TEST_CASE (verify_markers)
2526 verify_markers_test (
2527 "build/test/verify_markers_all_correct",
2529 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2530 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2531 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2532 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2537 verify_markers_test (
2538 "build/test/verify_markers_missing_ffec",
2540 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2541 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2542 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2548 verify_markers_test (
2549 "build/test/verify_markers_missing_ffmc",
2551 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2552 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2553 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2556 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2559 verify_markers_test (
2560 "build/test/verify_markers_missing_ffoc",
2562 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2563 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2564 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2567 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2570 verify_markers_test (
2571 "build/test/verify_markers_missing_lfoc",
2573 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2574 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2575 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2578 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2581 verify_markers_test (
2582 "build/test/verify_markers_incorrect_ffoc",
2584 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2585 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2586 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2587 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2590 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2593 verify_markers_test (
2594 "build/test/verify_markers_incorrect_lfoc",
2596 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2597 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2598 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2599 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2602 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2607 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2609 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2610 prepare_directory (dir);
2611 auto dcp = make_simple (dir);
2612 auto cpl = dcp->cpls()[0];
2613 cpl->unset_version_number();
2616 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2620 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2622 path dir = "build/test/verify_missing_extension_metadata1";
2623 auto dcp = make_simple (dir);
2626 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2627 auto cpl = dcp->cpls()[0];
2630 Editor e (cpl->file().get());
2631 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2634 check_verify_result (
2637 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2638 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2643 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2645 path dir = "build/test/verify_missing_extension_metadata2";
2646 auto dcp = make_simple (dir);
2649 auto cpl = dcp->cpls()[0];
2652 Editor e (cpl->file().get());
2653 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2656 check_verify_result (
2659 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2660 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2665 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2667 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2668 auto dcp = make_simple (dir);
2671 auto const cpl = dcp->cpls()[0];
2674 Editor e (cpl->file().get());
2675 e.replace ("<meta:Name>A", "<meta:NameX>A");
2676 e.replace ("n</meta:Name>", "n</meta:NameX>");
2679 check_verify_result (
2682 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2683 { 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 },
2684 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2689 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2691 path dir = "build/test/verify_invalid_extension_metadata1";
2692 auto dcp = make_simple (dir);
2695 auto cpl = dcp->cpls()[0];
2698 Editor e (cpl->file().get());
2699 e.replace ("Application", "Fred");
2702 check_verify_result (
2705 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2711 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2713 path dir = "build/test/verify_invalid_extension_metadata2";
2714 auto dcp = make_simple (dir);
2717 auto cpl = dcp->cpls()[0];
2720 Editor e (cpl->file().get());
2721 e.replace ("DCP Constraints Profile", "Fred");
2724 check_verify_result (
2727 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2733 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2735 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2736 auto dcp = make_simple (dir);
2739 auto const cpl = dcp->cpls()[0];
2742 Editor e (cpl->file().get());
2743 e.replace ("<meta:Value>", "<meta:ValueX>");
2744 e.replace ("</meta:Value>", "</meta:ValueX>");
2747 check_verify_result (
2750 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2751 { 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 },
2752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2757 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2759 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2760 auto dcp = make_simple (dir);
2763 auto const cpl = dcp->cpls()[0];
2766 Editor e (cpl->file().get());
2767 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2770 check_verify_result (
2773 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2774 { 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() },
2779 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2781 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2782 auto dcp = make_simple (dir);
2785 auto const cpl = dcp->cpls()[0];
2788 Editor e (cpl->file().get());
2789 e.replace ("<meta:Property>", "<meta:PropertyX>");
2790 e.replace ("</meta:Property>", "</meta:PropertyX>");
2793 check_verify_result (
2796 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2798 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2803 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2805 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2806 auto dcp = make_simple (dir);
2809 auto const cpl = dcp->cpls()[0];
2812 Editor e (cpl->file().get());
2813 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2814 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2817 check_verify_result (
2820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2821 { 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 },
2822 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2828 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2830 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2831 prepare_directory (dir);
2832 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2833 copy_file (i.path(), dir / i.path().filename());
2836 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2837 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2841 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2844 check_verify_result (
2847 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2848 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2849 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2851 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2852 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2859 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2861 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2862 prepare_directory (dir);
2863 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2864 copy_file (i.path(), dir / i.path().filename());
2867 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2868 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2871 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2874 check_verify_result (
2877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2878 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2879 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2880 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2881 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2882 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2888 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2890 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2891 prepare_directory (dir);
2892 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2893 copy_file (i.path(), dir / i.path().filename());
2897 Editor e (dir / dcp_test1_pkl);
2898 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2901 check_verify_result ({dir}, {});
2905 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2907 path dir ("build/test/verify_must_not_be_partially_encrypted");
2908 prepare_directory (dir);
2912 auto signer = make_shared<dcp::CertificateChain>();
2913 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2914 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2915 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2916 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2918 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2922 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2925 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
2926 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2927 for (int i = 0; i < 24; ++i) {
2928 writer->write (j2c.data(), j2c.size());
2930 writer->finalize ();
2932 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2934 auto reel = make_shared<dcp::Reel>(
2935 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2936 make_shared<dcp::ReelSoundAsset>(ms, 0)
2939 reel->add (simple_markers());
2943 cpl->set_content_version (
2944 {"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"}
2946 cpl->set_annotation_text ("A Test DCP");
2947 cpl->set_issuer ("OpenDCP 0.0.25");
2948 cpl->set_creator ("OpenDCP 0.0.25");
2949 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2950 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
2951 cpl->set_main_sound_sample_rate (48000);
2952 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2953 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2954 cpl->set_version_number (1);
2958 d.set_issuer("OpenDCP 0.0.25");
2959 d.set_creator("OpenDCP 0.0.25");
2960 d.set_issue_date("2012-07-17T04:45:18+00:00");
2961 d.set_annotation_text("A Test DCP");
2962 d.write_xml(signer);
2964 check_verify_result (
2967 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2972 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2974 vector<dcp::VerificationNote> notes;
2975 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"));
2976 auto reader = picture.start_read ();
2977 auto frame = reader->get_frame (0);
2978 verify_j2k(frame, 0, 24, notes);
2979 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2983 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2985 vector<dcp::VerificationNote> notes;
2986 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2987 auto reader = picture.start_read ();
2988 auto frame = reader->get_frame (0);
2989 verify_j2k(frame, 0, 24, notes);
2990 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2994 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2996 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2997 prepare_directory (dir);
2998 auto dcp = make_simple (dir);
3000 vector<dcp::VerificationNote> notes;
3001 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3002 auto reader = picture.start_read ();
3003 auto frame = reader->get_frame (0);
3004 verify_j2k(frame, 0, 24, notes);
3005 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3009 /** Check that ResourceID and the XML ID being different is spotted */
3010 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3012 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3013 prepare_directory (dir);
3015 ASDCP::WriterInfo writer_info;
3016 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3019 auto mxf_id = dcp::make_uuid ();
3020 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3021 BOOST_REQUIRE (c == Kumu::UUID_Length);
3023 auto resource_id = dcp::make_uuid ();
3024 ASDCP::TimedText::TimedTextDescriptor descriptor;
3025 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3026 DCP_ASSERT (c == Kumu::UUID_Length);
3028 auto xml_id = dcp::make_uuid ();
3029 ASDCP::TimedText::MXFWriter writer;
3030 auto subs_mxf = dir / "subs.mxf";
3031 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3032 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3033 writer.WriteTimedTextResource (dcp::String::compose(
3034 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3035 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3036 "<Id>urn:uuid:%1</Id>"
3037 "<ContentTitleText>Content</ContentTitleText>"
3038 "<AnnotationText>Annotation</AnnotationText>"
3039 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3040 "<ReelNumber>1</ReelNumber>"
3041 "<Language>en-US</Language>"
3042 "<EditRate>25 1</EditRate>"
3043 "<TimeCodeRate>25</TimeCodeRate>"
3044 "<StartTime>00:00:00:00</StartTime>"
3045 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3047 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3048 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3049 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3058 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3059 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3061 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3063 check_verify_result (
3066 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3067 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3068 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3074 /** Check that ResourceID and the MXF ID being the same is spotted */
3075 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3077 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3078 prepare_directory (dir);
3080 ASDCP::WriterInfo writer_info;
3081 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3084 auto mxf_id = dcp::make_uuid ();
3085 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3086 BOOST_REQUIRE (c == Kumu::UUID_Length);
3088 auto resource_id = mxf_id;
3089 ASDCP::TimedText::TimedTextDescriptor descriptor;
3090 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3091 DCP_ASSERT (c == Kumu::UUID_Length);
3093 auto xml_id = resource_id;
3094 ASDCP::TimedText::MXFWriter writer;
3095 auto subs_mxf = dir / "subs.mxf";
3096 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3097 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3098 writer.WriteTimedTextResource (dcp::String::compose(
3099 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3100 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3101 "<Id>urn:uuid:%1</Id>"
3102 "<ContentTitleText>Content</ContentTitleText>"
3103 "<AnnotationText>Annotation</AnnotationText>"
3104 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3105 "<ReelNumber>1</ReelNumber>"
3106 "<Language>en-US</Language>"
3107 "<EditRate>25 1</EditRate>"
3108 "<TimeCodeRate>25</TimeCodeRate>"
3109 "<StartTime>00:00:00:00</StartTime>"
3110 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3112 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3113 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3114 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3123 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3124 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3126 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3128 check_verify_result (
3131 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3133 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3134 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3135 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3140 /** Check a DCP with a 3D asset marked as 2D */
3141 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3143 check_verify_result (
3144 { private_test / "data" / "xm" },
3147 dcp::VerificationNote::Type::WARNING,
3148 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3151 dcp::VerificationNote::Type::BV21_ERROR,
3152 dcp::VerificationNote::Code::INVALID_STANDARD
3159 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3161 path dir = "build/test/verify_unexpected_things_in_main_markers";
3162 prepare_directory (dir);
3163 auto dcp = make_simple (dir, 1, 24);
3167 Editor e (find_cpl(dir));
3169 " <IntrinsicDuration>24</IntrinsicDuration>",
3170 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3174 dcp::CPL cpl (find_cpl(dir));
3176 check_verify_result (
3179 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3180 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3181 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3186 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3188 path dir = "build/test/verify_invalid_content_kind";
3189 prepare_directory (dir);
3190 auto dcp = make_simple (dir, 1, 24);
3194 Editor e(find_cpl(dir));
3195 e.replace("trailer", "trip");
3198 dcp::CPL cpl (find_cpl(dir));
3200 check_verify_result (
3203 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3204 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3210 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3212 path dir = "build/test/verify_valid_content_kind";
3213 prepare_directory (dir);
3214 auto dcp = make_simple (dir, 1, 24);
3218 Editor e(find_cpl(dir));
3219 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3222 dcp::CPL cpl (find_cpl(dir));
3224 check_verify_result (
3227 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3233 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3235 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3236 prepare_directory(dir);
3237 auto dcp = make_simple(dir, 1, 24);
3240 auto constexpr area = "<meta:MainPictureActiveArea>";
3243 Editor e(find_cpl(dir));
3244 e.delete_lines_after(area, 2);
3245 e.insert(area, "<meta:Height>4080</meta:Height>");
3246 e.insert(area, "<meta:Width>1997</meta:Width>");
3249 dcp::PKL pkl(find_pkl(dir));
3250 dcp::CPL cpl(find_cpl(dir));
3252 check_verify_result(
3255 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3256 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3257 { 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)) },
3262 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3264 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3265 prepare_directory(dir);
3266 auto dcp = make_simple(dir, 1, 24);
3269 auto constexpr area = "<meta:MainPictureActiveArea>";
3272 Editor e(find_cpl(dir));
3273 e.delete_lines_after(area, 2);
3274 e.insert(area, "<meta:Height>5125</meta:Height>");
3275 e.insert(area, "<meta:Width>9900</meta:Width>");
3278 dcp::PKL pkl(find_pkl(dir));
3279 dcp::CPL cpl(find_cpl(dir));
3281 check_verify_result(
3284 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3285 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3286 { 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)) },
3287 { 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)) },
3292 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3296 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3297 prepare_directory(dir);
3298 auto dcp = make_simple(dir, 1, 24);
3302 Editor e(find_pkl(dir));
3303 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3306 dcp::PKL pkl(find_pkl(dir));
3308 check_verify_result(
3311 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3316 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3320 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3321 prepare_directory(dir);
3322 auto dcp = make_simple(dir, 1, 24);
3326 Editor e(find_asset_map(dir));
3327 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3330 dcp::PKL pkl(find_pkl(dir));
3331 dcp::AssetMap asset_map(find_asset_map(dir));
3333 check_verify_result(
3336 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3337 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3342 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3344 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3346 dcp::MXFMetadata mxf_meta;
3347 mxf_meta.company_name = "OpenDCP";
3348 mxf_meta.product_name = "OpenDCP";
3349 mxf_meta.product_version = "0.0.25";
3351 auto constexpr sample_rate = 48000;
3352 auto constexpr frames = 240;
3354 boost::filesystem::remove_all(path);
3355 boost::filesystem::create_directories(path);
3356 auto dcp = make_shared<dcp::DCP>(path);
3357 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3358 cpl->set_annotation_text("hello");
3359 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3360 cpl->set_main_sound_sample_rate(sample_rate);
3361 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3362 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3363 cpl->set_version_number(1);
3367 /* Reel with 2 channels of audio */
3369 auto mp = simple_picture(path, "1", frames, {});
3370 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3372 auto reel = make_shared<dcp::Reel>(
3373 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3374 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3377 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3378 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3385 /* Reel with 6 channels of audio */
3387 auto mp = simple_picture(path, "2", frames, {});
3388 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3390 auto reel = make_shared<dcp::Reel>(
3391 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3392 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3395 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3396 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3403 dcp->set_annotation_text("hello");
3406 check_verify_result(
3409 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3414 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3416 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3418 dcp::MXFMetadata mxf_meta;
3419 mxf_meta.company_name = "OpenDCP";
3420 mxf_meta.product_name = "OpenDCP";
3421 mxf_meta.product_version = "0.0.25";
3423 auto constexpr sample_rate = 48000;
3424 auto constexpr frames = 240;
3426 boost::filesystem::remove_all(path);
3427 boost::filesystem::create_directories(path);
3428 auto dcp = make_shared<dcp::DCP>(path);
3429 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3430 cpl->set_annotation_text("hello");
3431 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3432 cpl->set_main_sound_sample_rate(sample_rate);
3433 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3434 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3435 cpl->set_version_number(1);
3437 auto mp = simple_picture(path, "1", frames, {});
3438 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3440 auto reel = make_shared<dcp::Reel>(
3441 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3442 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3445 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3446 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3447 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3453 dcp->set_annotation_text("hello");
3456 check_verify_result(
3459 { 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)) },
3464 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3466 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3467 auto constexpr video_frames = 24;
3468 auto constexpr sample_rate = 48000;
3470 boost::filesystem::remove_all(path);
3471 boost::filesystem::create_directories(path);
3473 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3474 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3476 dcp::Size const size(1998, 1080);
3477 auto image = make_shared<dcp::OpenJPEGImage>(size);
3478 boost::random::mt19937 rng(1);
3479 boost::random::uniform_int_distribution<> dist(0, 4095);
3480 for (int c = 0; c < 3; ++c) {
3481 for (int p = 0; p < (1998 * 1080); ++p) {
3482 image->data(c)[p] = dist(rng);
3485 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3486 for (int i = 0; i < 24; ++i) {
3487 picture_writer->write(j2c.data(), j2c.size());
3489 picture_writer->finalize();
3491 auto dcp = make_shared<dcp::DCP>(path);
3492 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3493 cpl->set_content_version(
3494 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3496 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3497 cpl->set_main_sound_sample_rate(sample_rate);
3498 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3499 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3500 cpl->set_version_number(1);
3502 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3504 auto reel = make_shared<dcp::Reel>(
3505 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3506 make_shared<dcp::ReelSoundAsset>(ms, 0)
3511 dcp->set_annotation_text("A Test DCP");
3514 check_verify_result(
3517 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3518 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3519 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3520 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3525 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3527 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3528 check_verify_result(
3531 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3532 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3533 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3535 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3536 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3541 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3543 path const dir("build/test/verify_missing_load_font");
3544 prepare_directory (dir);
3545 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3547 Editor editor(dir / "subs.xml");
3548 editor.delete_first_line_containing("LoadFont");
3550 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3551 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3552 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3554 check_verify_result (
3556 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3557 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3563 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3565 boost::filesystem::path const dir = dcp::String::compose("build/test/%1", boost::unit_test::framework::current_test_case().full_name());
3566 prepare_directory(dir);
3567 auto dcp = make_simple (dir, 1, 202);
3570 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3571 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3572 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3573 "<ContentTitleText>Content</ContentTitleText>"
3574 "<AnnotationText>Annotation</AnnotationText>"
3575 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3576 "<ReelNumber>1</ReelNumber>"
3577 "<EditRate>24 1</EditRate>"
3578 "<TimeCodeRate>24</TimeCodeRate>"
3579 "<StartTime>00:00:00:00</StartTime>"
3580 "<Language>de-DE</Language>"
3582 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3583 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3584 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3590 dcp::File xml_file(dir / "subs.xml", "w");
3591 BOOST_REQUIRE(xml_file);
3592 xml_file.write(xml.c_str(), xml.size(), 1);
3594 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3595 subs->write(dir / "subs.mxf");
3597 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3598 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3601 check_verify_result (
3604 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3609 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3611 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3612 boost::filesystem::remove_all(dir);
3614 auto dcp1 = make_simple(dir / "1");
3617 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3619 auto dcp2 = make_simple(dir / "2");
3621 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3623 boost::filesystem::remove(dir / "1" / "video.mxf");
3624 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3626 check_verify_result(
3629 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3634 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3636 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3637 boost::filesystem::remove_all(dir);
3639 auto dcp = make_simple(dir);
3640 BOOST_REQUIRE(dcp->cpls().size() == 1);
3641 auto cpl = dcp->cpls()[0];
3642 cpl->set_content_version(dcp::ContentVersion(""));
3645 check_verify_result(
3648 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())