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");
544 check_verify_result (
547 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
548 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
549 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
550 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
551 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
552 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
559 dcp_from_frame (dcp::ArrayData const& frame, path dir)
561 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
562 create_directories (dir);
563 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
564 for (int i = 0; i < 24; ++i) {
565 writer->write (frame.data(), frame.size());
569 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
570 return write_dcp_with_single_asset (dir, reel_asset);
574 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
576 int const too_big = 1302083 * 2;
578 /* Compress a black image */
579 auto image = black_image ();
580 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
581 BOOST_REQUIRE (frame.size() < too_big);
583 /* Place it in a bigger block with some zero padding at the end */
584 dcp::ArrayData oversized_frame(too_big);
585 memcpy (oversized_frame.data(), frame.data(), frame.size());
586 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
588 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
589 prepare_directory (dir);
590 auto cpl = dcp_from_frame (oversized_frame, dir);
592 check_verify_result (
595 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
596 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
602 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
604 int const nearly_too_big = 1302083 * 0.98;
606 /* Compress a black image */
607 auto image = black_image ();
608 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
609 BOOST_REQUIRE (frame.size() < nearly_too_big);
611 /* Place it in a bigger block with some zero padding at the end */
612 dcp::ArrayData oversized_frame(nearly_too_big);
613 memcpy (oversized_frame.data(), frame.data(), frame.size());
614 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
616 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
617 prepare_directory (dir);
618 auto cpl = dcp_from_frame (oversized_frame, dir);
620 check_verify_result (
623 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
624 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
625 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
630 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
632 /* Compress a black image */
633 auto image = black_image ();
634 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
635 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
637 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
638 prepare_directory (dir);
639 auto cpl = dcp_from_frame (frame, dir);
641 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
645 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
647 path const dir("build/test/verify_valid_interop_subtitles");
648 prepare_directory (dir);
649 copy_file ("test/data/subs1.xml", dir / "subs.xml");
650 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
651 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
652 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
654 check_verify_result (
656 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
657 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
662 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
664 using namespace boost::filesystem;
666 path const dir("build/test/verify_invalid_interop_subtitles");
667 prepare_directory (dir);
668 copy_file ("test/data/subs1.xml", dir / "subs.xml");
669 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
670 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
671 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
674 Editor e (dir / "subs.xml");
675 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
678 check_verify_result (
681 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
682 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
684 dcp::VerificationNote::Type::ERROR,
685 dcp::VerificationNote::Code::INVALID_XML,
686 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
690 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
695 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
697 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
698 prepare_directory(dir);
699 copy_file("test/data/subs4.xml", dir / "subs.xml");
700 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
701 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
702 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
704 check_verify_result (
707 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
708 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
709 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
715 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
717 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
718 prepare_directory(dir);
719 copy_file("test/data/subs5.xml", dir / "subs.xml");
720 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
721 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
722 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
724 check_verify_result (
727 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
734 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
736 path const dir("build/test/verify_valid_smpte_subtitles");
737 prepare_directory (dir);
738 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
739 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
740 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
741 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
746 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
747 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
748 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
753 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
755 using namespace boost::filesystem;
757 path const dir("build/test/verify_invalid_smpte_subtitles");
758 prepare_directory (dir);
759 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
760 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
761 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
762 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
763 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
765 check_verify_result (
768 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
770 dcp::VerificationNote::Type::ERROR,
771 dcp::VerificationNote::Code::INVALID_XML,
772 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
778 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
779 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
784 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
786 path const dir("build/test/verify_empty_text_node_in_subtitles");
787 prepare_directory (dir);
788 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
789 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
790 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
791 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
793 check_verify_result (
796 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
797 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
799 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
800 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
801 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
806 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
807 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
809 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
810 prepare_directory (dir);
811 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
812 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
813 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
814 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
816 check_verify_result (
819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
825 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
826 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
828 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
829 prepare_directory (dir);
830 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
831 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
832 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
833 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
835 check_verify_result (
838 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
839 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
840 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
841 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
846 BOOST_AUTO_TEST_CASE (verify_external_asset)
848 path const ov_dir("build/test/verify_external_asset");
849 prepare_directory (ov_dir);
851 auto image = black_image ();
852 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
853 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
854 dcp_from_frame (frame, ov_dir);
856 dcp::DCP ov (ov_dir);
859 path const vf_dir("build/test/verify_external_asset_vf");
860 prepare_directory (vf_dir);
862 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
863 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
865 check_verify_result (
868 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
869 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
874 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
876 path const dir("build/test/verify_valid_cpl_metadata");
877 prepare_directory (dir);
879 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
880 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
881 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
883 auto reel = make_shared<dcp::Reel>();
884 reel->add (reel_asset);
886 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
887 reel->add (simple_markers(16 * 24));
889 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
891 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
892 cpl->set_main_sound_sample_rate (48000);
893 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
894 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
895 cpl->set_version_number (1);
899 dcp.set_annotation_text("hello");
905 find_prefix(path dir, string prefix)
907 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
908 return boost::starts_with(p.filename().string(), prefix);
911 BOOST_REQUIRE(iter != directory_iterator());
916 path find_cpl (path dir)
918 return find_prefix(dir, "cpl_");
925 return find_prefix(dir, "pkl_");
930 find_asset_map(path dir)
932 return find_prefix(dir, "ASSETMAP");
936 /* DCP with invalid CompositionMetadataAsset */
937 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
939 using namespace boost::filesystem;
941 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
942 prepare_directory (dir);
944 auto reel = make_shared<dcp::Reel>();
945 reel->add (black_picture_asset(dir));
946 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
948 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
949 cpl->set_main_sound_sample_rate (48000);
950 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
951 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
952 cpl->set_version_number (1);
954 reel->add (simple_markers());
958 dcp.set_annotation_text("hello");
962 Editor e (find_cpl(dir));
963 e.replace ("MainSound", "MainSoundX");
966 check_verify_result (
969 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
970 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
972 dcp::VerificationNote::Type::ERROR,
973 dcp::VerificationNote::Code::INVALID_XML,
974 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
975 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
976 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
977 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
978 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
979 "ExtensionMetadataList?,)'"),
980 canonical(cpl->file().get()),
983 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
988 /* DCP with invalid CompositionMetadataAsset */
989 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
991 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
992 prepare_directory (dir);
994 auto reel = make_shared<dcp::Reel>();
995 reel->add (black_picture_asset(dir));
996 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
998 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
999 cpl->set_main_sound_sample_rate (48000);
1000 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1001 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1005 dcp.set_annotation_text("hello");
1009 Editor e (find_cpl(dir));
1010 e.replace ("meta:Width", "meta:WidthX");
1013 check_verify_result (
1015 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1020 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1022 path const dir("build/test/verify_invalid_language1");
1023 prepare_directory (dir);
1024 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1025 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1026 asset->_language = "wrong-andbad";
1027 asset->write (dir / "subs.mxf");
1028 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1029 reel_asset->_language = "badlang";
1030 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1032 check_verify_result (
1035 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1036 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1037 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1042 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1043 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1045 path const dir("build/test/verify_invalid_language2");
1046 prepare_directory (dir);
1047 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1048 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1049 asset->_language = "wrong-andbad";
1050 asset->write (dir / "subs.mxf");
1051 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1052 reel_asset->_language = "badlang";
1053 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1055 check_verify_result (
1058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1060 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1065 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1066 * the release territory.
1068 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1070 path const dir("build/test/verify_invalid_language3");
1071 prepare_directory (dir);
1073 auto picture = simple_picture (dir, "foo");
1074 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1075 auto reel = make_shared<dcp::Reel>();
1076 reel->add (reel_picture);
1077 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1078 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1079 reel->add (reel_sound);
1080 reel->add (simple_markers());
1082 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1084 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1085 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1086 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1087 cpl->set_main_sound_sample_rate (48000);
1088 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1089 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1090 cpl->set_version_number (1);
1091 cpl->_release_territory = "fred-jim";
1092 auto dcp = make_shared<dcp::DCP>(dir);
1094 dcp->set_annotation_text("hello");
1097 check_verify_result (
1100 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1101 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1102 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1103 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1109 vector<dcp::VerificationNote>
1110 check_picture_size (int width, int height, int frame_rate, bool three_d)
1112 using namespace boost::filesystem;
1114 path dcp_path = "build/test/verify_picture_test";
1115 prepare_directory (dcp_path);
1117 shared_ptr<dcp::PictureAsset> mp;
1119 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1121 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1123 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1125 auto image = black_image (dcp::Size(width, height));
1126 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1127 int const length = three_d ? frame_rate * 2 : frame_rate;
1128 for (int i = 0; i < length; ++i) {
1129 picture_writer->write (j2c.data(), j2c.size());
1131 picture_writer->finalize ();
1133 auto d = make_shared<dcp::DCP>(dcp_path);
1134 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1135 cpl->set_annotation_text ("A Test DCP");
1136 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1137 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1138 cpl->set_main_sound_sample_rate (48000);
1139 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1140 cpl->set_main_picture_active_area(dcp::Size(width, height));
1141 cpl->set_version_number (1);
1143 auto reel = make_shared<dcp::Reel>();
1146 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1148 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1151 reel->add (simple_markers(frame_rate));
1156 d->set_annotation_text("A Test DCP");
1159 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1165 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1167 auto notes = check_picture_size(width, height, frame_rate, three_d);
1168 BOOST_CHECK_EQUAL (notes.size(), 0U);
1174 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1176 auto notes = check_picture_size(width, height, frame_rate, three_d);
1177 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1178 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1179 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1185 check_picture_size_bad_2k_frame_rate (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(), 2U);
1189 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1190 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1196 check_picture_size_bad_4k_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(), 1U);
1200 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1201 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1205 BOOST_AUTO_TEST_CASE (verify_picture_size)
1207 using namespace boost::filesystem;
1210 check_picture_size_ok (2048, 858, 24, false);
1211 check_picture_size_ok (2048, 858, 25, false);
1212 check_picture_size_ok (2048, 858, 48, false);
1213 check_picture_size_ok (2048, 858, 24, true);
1214 check_picture_size_ok (2048, 858, 25, true);
1215 check_picture_size_ok (2048, 858, 48, true);
1218 check_picture_size_ok (1998, 1080, 24, false);
1219 check_picture_size_ok (1998, 1080, 25, false);
1220 check_picture_size_ok (1998, 1080, 48, false);
1221 check_picture_size_ok (1998, 1080, 24, true);
1222 check_picture_size_ok (1998, 1080, 25, true);
1223 check_picture_size_ok (1998, 1080, 48, true);
1226 check_picture_size_ok (4096, 1716, 24, false);
1229 check_picture_size_ok (3996, 2160, 24, false);
1231 /* Bad frame size */
1232 check_picture_size_bad_frame_size (2050, 858, 24, false);
1233 check_picture_size_bad_frame_size (2048, 658, 25, false);
1234 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1235 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1237 /* Bad 2K frame rate */
1238 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1239 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1240 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1242 /* Bad 4K frame rate */
1243 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1244 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1247 auto notes = check_picture_size(3996, 2160, 24, true);
1248 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1249 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1250 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1256 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")
1259 std::make_shared<dcp::SubtitleString>(
1267 dcp::Time(start_frame, 24, 24),
1268 dcp::Time(end_frame, 24, 24),
1270 dcp::HAlign::CENTER,
1274 dcp::Direction::LTR,
1286 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1288 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1289 prepare_directory (dir);
1291 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1292 for (int i = 0; i < 2048; ++i) {
1293 add_test_subtitle (asset, i * 24, i * 24 + 20);
1296 asset->set_language (dcp::LanguageTag("de-DE"));
1297 asset->write (dir / "subs.mxf");
1298 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1299 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1301 check_verify_result (
1304 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1306 dcp::VerificationNote::Type::BV21_ERROR,
1307 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1309 canonical(dir / "subs.mxf")
1311 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1312 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1318 shared_ptr<dcp::SMPTESubtitleAsset>
1319 make_large_subtitle_asset (path font_file)
1321 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1322 dcp::ArrayData big_fake_font(1024 * 1024);
1323 big_fake_font.write (font_file);
1324 for (int i = 0; i < 116; ++i) {
1325 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1333 verify_timed_text_asset_too_large (string name)
1335 auto const dir = path("build/test") / name;
1336 prepare_directory (dir);
1337 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1338 add_test_subtitle (asset, 0, 240);
1339 asset->set_language (dcp::LanguageTag("de-DE"));
1340 asset->write (dir / "subs.mxf");
1342 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1343 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1345 check_verify_result (
1348 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1349 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1350 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1351 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1352 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1357 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1359 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1360 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1364 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1366 path dir = "build/test/verify_missing_subtitle_language";
1367 prepare_directory (dir);
1368 auto dcp = make_simple (dir, 1, 106);
1371 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1372 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1373 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1374 "<ContentTitleText>Content</ContentTitleText>"
1375 "<AnnotationText>Annotation</AnnotationText>"
1376 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1377 "<ReelNumber>1</ReelNumber>"
1378 "<EditRate>24 1</EditRate>"
1379 "<TimeCodeRate>24</TimeCodeRate>"
1380 "<StartTime>00:00:00:00</StartTime>"
1381 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1383 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1384 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1385 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1391 dcp::File xml_file(dir / "subs.xml", "w");
1392 BOOST_REQUIRE (xml_file);
1393 xml_file.write(xml.c_str(), xml.size(), 1);
1395 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1396 subs->write (dir / "subs.mxf");
1398 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1399 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1402 check_verify_result (
1405 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1406 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1411 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1413 path path ("build/test/verify_mismatched_subtitle_languages");
1414 auto constexpr reel_length = 192;
1415 auto dcp = make_simple (path, 2, reel_length);
1416 auto cpl = dcp->cpls()[0];
1419 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1420 subs->set_language (dcp::LanguageTag("de-DE"));
1421 subs->add (simple_subtitle());
1423 subs->write (path / "subs1.mxf");
1424 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1425 cpl->reels()[0]->add(reel_subs);
1429 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1430 subs->set_language (dcp::LanguageTag("en-US"));
1431 subs->add (simple_subtitle());
1433 subs->write (path / "subs2.mxf");
1434 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1435 cpl->reels()[1]->add(reel_subs);
1440 check_verify_result (
1443 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1444 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1445 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1450 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1452 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1453 auto constexpr reel_length = 192;
1454 auto dcp = make_simple (path, 2, reel_length);
1455 auto cpl = dcp->cpls()[0];
1458 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1459 ccaps->set_language (dcp::LanguageTag("de-DE"));
1460 ccaps->add (simple_subtitle());
1462 ccaps->write (path / "subs1.mxf");
1463 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1464 cpl->reels()[0]->add(reel_ccaps);
1468 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1469 ccaps->set_language (dcp::LanguageTag("en-US"));
1470 ccaps->add (simple_subtitle());
1472 ccaps->write (path / "subs2.mxf");
1473 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1474 cpl->reels()[1]->add(reel_ccaps);
1479 check_verify_result (
1482 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1483 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1488 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1490 path dir = "build/test/verify_missing_subtitle_start_time";
1491 prepare_directory (dir);
1492 auto dcp = make_simple (dir, 1, 106);
1495 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1496 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1497 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1498 "<ContentTitleText>Content</ContentTitleText>"
1499 "<AnnotationText>Annotation</AnnotationText>"
1500 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1501 "<ReelNumber>1</ReelNumber>"
1502 "<Language>de-DE</Language>"
1503 "<EditRate>24 1</EditRate>"
1504 "<TimeCodeRate>24</TimeCodeRate>"
1505 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1507 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1508 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1509 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1515 dcp::File xml_file(dir / "subs.xml", "w");
1516 BOOST_REQUIRE (xml_file);
1517 xml_file.write(xml.c_str(), xml.size(), 1);
1519 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1520 subs->write (dir / "subs.mxf");
1522 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1523 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1526 check_verify_result (
1529 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1530 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1535 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1537 path dir = "build/test/verify_invalid_subtitle_start_time";
1538 prepare_directory (dir);
1539 auto dcp = make_simple (dir, 1, 106);
1542 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1543 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1544 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1545 "<ContentTitleText>Content</ContentTitleText>"
1546 "<AnnotationText>Annotation</AnnotationText>"
1547 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1548 "<ReelNumber>1</ReelNumber>"
1549 "<Language>de-DE</Language>"
1550 "<EditRate>24 1</EditRate>"
1551 "<TimeCodeRate>24</TimeCodeRate>"
1552 "<StartTime>00:00:02:00</StartTime>"
1553 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1555 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1556 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1557 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1563 dcp::File xml_file(dir / "subs.xml", "w");
1564 BOOST_REQUIRE (xml_file);
1565 xml_file.write(xml.c_str(), xml.size(), 1);
1567 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1568 subs->write (dir / "subs.mxf");
1570 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1571 dcp->cpls().front()->reels().front()->add(reel_subs);
1574 check_verify_result (
1577 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1578 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1586 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1589 , v_position(v_position_)
1597 dcp::VAlign v_align;
1603 shared_ptr<dcp::CPL>
1604 dcp_with_text (path dir, vector<TestText> subs)
1606 prepare_directory (dir);
1607 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1608 asset->set_start_time (dcp::Time());
1609 for (auto i: subs) {
1610 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1612 asset->set_language (dcp::LanguageTag("de-DE"));
1614 asset->write (dir / "subs.mxf");
1616 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1617 return write_dcp_with_single_asset (dir, reel_asset);
1622 shared_ptr<dcp::CPL>
1623 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1625 prepare_directory (dir);
1626 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1627 asset->set_start_time (dcp::Time());
1628 asset->set_language (dcp::LanguageTag("de-DE"));
1630 auto subs_mxf = dir / "subs.mxf";
1631 asset->write (subs_mxf);
1633 /* The call to write() puts the asset into the DCP correctly but it will have
1634 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1637 ASDCP::TimedText::MXFWriter writer;
1638 ASDCP::WriterInfo writer_info;
1639 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1641 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1642 DCP_ASSERT (c == Kumu::UUID_Length);
1643 ASDCP::TimedText::TimedTextDescriptor descriptor;
1644 descriptor.ContainerDuration = asset->intrinsic_duration();
1645 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1646 DCP_ASSERT (c == Kumu::UUID_Length);
1647 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1648 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1649 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1650 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1653 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1654 return write_dcp_with_single_asset (dir, reel_asset);
1658 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1660 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1661 /* Just too early */
1662 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1663 check_verify_result (
1666 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1667 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1673 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1675 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1676 /* Just late enough */
1677 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1678 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1682 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1684 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1685 prepare_directory (dir);
1687 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1688 asset1->set_start_time (dcp::Time());
1689 /* Just late enough */
1690 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1691 asset1->set_language (dcp::LanguageTag("de-DE"));
1693 asset1->write (dir / "subs1.mxf");
1694 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1695 auto reel1 = make_shared<dcp::Reel>();
1696 reel1->add (reel_asset1);
1697 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1698 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1699 reel1->add (markers1);
1701 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1702 asset2->set_start_time (dcp::Time());
1704 /* This would be too early on first reel but should be OK on the second */
1705 add_test_subtitle (asset2, 3, 4 * 24);
1706 asset2->set_language (dcp::LanguageTag("de-DE"));
1707 asset2->write (dir / "subs2.mxf");
1708 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1709 auto reel2 = make_shared<dcp::Reel>();
1710 reel2->add (reel_asset2);
1711 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1712 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1713 reel2->add (markers2);
1715 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1718 auto dcp = make_shared<dcp::DCP>(dir);
1720 dcp->set_annotation_text("hello");
1723 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1727 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1729 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1730 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1734 { 5 * 24 + 1, 6 * 24 },
1736 check_verify_result (
1739 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1740 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1745 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1747 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1748 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1752 { 5 * 24 + 16, 8 * 24 },
1754 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1758 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1760 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1761 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1762 check_verify_result (
1765 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1766 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1771 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1773 auto const dir = path("build/test/verify_valid_subtitle_duration");
1774 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1775 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1779 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1781 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1782 prepare_directory (dir);
1783 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1784 asset->set_start_time (dcp::Time());
1785 add_test_subtitle (asset, 0, 4 * 24);
1787 asset->set_language (dcp::LanguageTag("de-DE"));
1788 asset->write (dir / "subs.mxf");
1790 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1791 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1792 check_verify_result (
1795 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1796 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1804 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1806 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1807 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1810 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1811 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1812 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1813 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1815 check_verify_result (
1818 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1824 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1826 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1827 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1830 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1831 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1832 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1834 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1838 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1840 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1841 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1844 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1845 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1846 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1847 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1849 check_verify_result (
1852 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1858 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1860 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1861 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1864 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1865 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1866 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1867 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1869 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1873 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1875 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1876 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1879 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1881 check_verify_result (
1884 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1890 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1892 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1893 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1896 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1898 check_verify_result (
1901 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1907 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1909 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1910 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1913 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1914 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1915 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1916 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1918 check_verify_result (
1921 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1922 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1927 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1929 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1930 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1933 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1934 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1935 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1937 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1941 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1943 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1944 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1947 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1948 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1949 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1950 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1952 check_verify_result (
1955 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1956 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1961 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1963 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1964 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1967 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1968 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1969 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1970 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1972 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1976 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1978 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1979 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1982 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1984 check_verify_result (
1987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1992 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1994 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1995 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1998 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2000 check_verify_result (
2003 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2004 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2009 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2011 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2012 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2015 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2016 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2017 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2019 check_verify_result (
2022 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2027 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2029 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2030 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2033 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2034 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2035 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2037 check_verify_result (
2040 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2041 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2046 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2048 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2049 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2052 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2053 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2054 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2056 check_verify_result (
2059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2064 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2066 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2067 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2070 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2071 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2072 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2074 check_verify_result (
2077 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2082 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2084 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2085 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2086 check_verify_result (
2089 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2090 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2095 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2097 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2098 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2099 check_verify_result (
2102 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2108 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2110 path const dir("build/test/verify_invalid_sound_frame_rate");
2111 prepare_directory (dir);
2113 auto picture = simple_picture (dir, "foo");
2114 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2115 auto reel = make_shared<dcp::Reel>();
2116 reel->add (reel_picture);
2117 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2118 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2119 reel->add (reel_sound);
2120 reel->add (simple_markers());
2121 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2123 auto dcp = make_shared<dcp::DCP>(dir);
2125 dcp->set_annotation_text("hello");
2128 check_verify_result (
2131 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2137 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2139 path const dir("build/test/verify_missing_cpl_annotation_text");
2140 auto dcp = make_simple (dir);
2143 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2145 auto const cpl = dcp->cpls()[0];
2148 BOOST_REQUIRE (cpl->file());
2149 Editor e(cpl->file().get());
2150 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2153 check_verify_result (
2156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2157 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2162 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2164 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2165 auto dcp = make_simple (dir);
2168 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2169 auto const cpl = dcp->cpls()[0];
2172 BOOST_REQUIRE (cpl->file());
2173 Editor e(cpl->file().get());
2174 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2177 check_verify_result (
2180 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2181 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2186 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2188 path const dir("build/test/verify_mismatched_asset_duration");
2189 prepare_directory (dir);
2190 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2191 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2193 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2194 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2196 auto reel = make_shared<dcp::Reel>(
2197 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2198 make_shared<dcp::ReelSoundAsset>(ms, 0)
2201 reel->add (simple_markers());
2205 dcp->set_annotation_text("A Test DCP");
2208 check_verify_result (
2211 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2212 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2219 shared_ptr<dcp::CPL>
2220 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2222 prepare_directory (dir);
2223 auto dcp = make_shared<dcp::DCP>(dir);
2224 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2226 auto constexpr reel_length = 192;
2228 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2229 subs->set_language (dcp::LanguageTag("de-DE"));
2230 subs->set_start_time (dcp::Time());
2231 subs->add (simple_subtitle());
2233 subs->write (dir / "subs.mxf");
2234 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2236 auto reel1 = make_shared<dcp::Reel>(
2237 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2238 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2242 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2245 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2246 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2247 reel1->add (markers1);
2251 auto reel2 = make_shared<dcp::Reel>(
2252 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2253 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2257 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2260 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2261 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2262 reel2->add (markers2);
2267 dcp->set_annotation_text("A Test DCP");
2274 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2277 path dir ("build/test/missing_main_subtitle_from_some_reels");
2278 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2279 check_verify_result (
2282 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2283 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2289 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2290 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2291 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2295 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2296 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2297 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2303 shared_ptr<dcp::CPL>
2304 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2306 prepare_directory (dir);
2307 auto dcp = make_shared<dcp::DCP>(dir);
2308 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2310 auto constexpr reel_length = 192;
2312 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2313 subs->set_language (dcp::LanguageTag("de-DE"));
2314 subs->set_start_time (dcp::Time());
2315 subs->add (simple_subtitle());
2317 subs->write (dir / "subs.mxf");
2319 auto reel1 = make_shared<dcp::Reel>(
2320 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2321 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2324 for (int i = 0; i < caps_in_reel1; ++i) {
2325 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2328 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2329 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2330 reel1->add (markers1);
2334 auto reel2 = make_shared<dcp::Reel>(
2335 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2336 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2339 for (int i = 0; i < caps_in_reel2; ++i) {
2340 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2343 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2344 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2345 reel2->add (markers2);
2350 dcp->set_annotation_text("A Test DCP");
2357 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2360 path dir ("build/test/mismatched_closed_caption_asset_counts");
2361 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2362 check_verify_result (
2365 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2366 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2371 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2372 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2373 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2377 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2378 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2379 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2386 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2388 prepare_directory (dir);
2389 auto dcp = make_shared<dcp::DCP>(dir);
2390 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2392 auto constexpr reel_length = 192;
2394 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2395 subs->set_language (dcp::LanguageTag("de-DE"));
2396 subs->set_start_time (dcp::Time());
2397 subs->add (simple_subtitle());
2399 subs->write (dir / "subs.mxf");
2400 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2403 auto reel = make_shared<dcp::Reel>(
2404 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2405 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2408 reel->add (reel_text);
2410 reel->add (simple_markers(reel_length));
2415 dcp->set_annotation_text("A Test DCP");
2418 check_verify_result (
2421 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2422 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2427 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2429 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2430 "build/test/verify_subtitle_entry_point_must_be_present",
2431 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2432 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2433 asset->unset_entry_point ();
2437 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2438 "build/test/verify_subtitle_entry_point_must_be_zero",
2439 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2440 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2441 asset->set_entry_point (4);
2445 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2446 "build/test/verify_closed_caption_entry_point_must_be_present",
2447 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2448 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2449 asset->unset_entry_point ();
2453 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2454 "build/test/verify_closed_caption_entry_point_must_be_zero",
2455 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2456 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2457 asset->set_entry_point (9);
2463 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2467 path const dir("build/test/verify_missing_hash");
2468 auto dcp = make_simple (dir);
2471 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2472 auto const cpl = dcp->cpls()[0];
2473 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2474 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2475 auto asset_id = cpl->reels()[0]->main_picture()->id();
2478 BOOST_REQUIRE (cpl->file());
2479 Editor e(cpl->file().get());
2480 e.delete_first_line_containing("<Hash>");
2483 check_verify_result (
2486 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2487 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2494 verify_markers_test (
2496 vector<pair<dcp::Marker, dcp::Time>> markers,
2497 vector<dcp::VerificationNote> test_notes
2500 auto dcp = make_simple (dir);
2501 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2502 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2503 for (auto const& i: markers) {
2504 markers_asset->set (i.first, i.second);
2506 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2509 check_verify_result ({dir}, test_notes);
2513 BOOST_AUTO_TEST_CASE (verify_markers)
2515 verify_markers_test (
2516 "build/test/verify_markers_all_correct",
2518 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2519 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2520 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2521 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2526 verify_markers_test (
2527 "build/test/verify_markers_missing_ffec",
2529 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2530 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2531 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2537 verify_markers_test (
2538 "build/test/verify_markers_missing_ffmc",
2540 { dcp::Marker::FFEC, dcp::Time(12, 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_FFMC_IN_FEATURE }
2548 verify_markers_test (
2549 "build/test/verify_markers_missing_ffoc",
2551 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2552 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2553 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2556 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2559 verify_markers_test (
2560 "build/test/verify_markers_missing_lfoc",
2562 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2563 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2564 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2567 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2570 verify_markers_test (
2571 "build/test/verify_markers_incorrect_ffoc",
2573 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2574 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2575 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2576 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2579 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2582 verify_markers_test (
2583 "build/test/verify_markers_incorrect_lfoc",
2585 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2586 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2587 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2588 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2591 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2596 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2598 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2599 prepare_directory (dir);
2600 auto dcp = make_simple (dir);
2601 auto cpl = dcp->cpls()[0];
2602 cpl->unset_version_number();
2605 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2609 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2611 path dir = "build/test/verify_missing_extension_metadata1";
2612 auto dcp = make_simple (dir);
2615 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2616 auto cpl = dcp->cpls()[0];
2619 Editor e (cpl->file().get());
2620 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2623 check_verify_result (
2626 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2627 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2632 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2634 path dir = "build/test/verify_missing_extension_metadata2";
2635 auto dcp = make_simple (dir);
2638 auto cpl = dcp->cpls()[0];
2641 Editor e (cpl->file().get());
2642 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2645 check_verify_result (
2648 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2649 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2654 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2656 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2657 auto dcp = make_simple (dir);
2660 auto const cpl = dcp->cpls()[0];
2663 Editor e (cpl->file().get());
2664 e.replace ("<meta:Name>A", "<meta:NameX>A");
2665 e.replace ("n</meta:Name>", "n</meta:NameX>");
2668 check_verify_result (
2671 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2672 { 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 },
2673 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2678 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2680 path dir = "build/test/verify_invalid_extension_metadata1";
2681 auto dcp = make_simple (dir);
2684 auto cpl = dcp->cpls()[0];
2687 Editor e (cpl->file().get());
2688 e.replace ("Application", "Fred");
2691 check_verify_result (
2694 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2695 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2700 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2702 path dir = "build/test/verify_invalid_extension_metadata2";
2703 auto dcp = make_simple (dir);
2706 auto cpl = dcp->cpls()[0];
2709 Editor e (cpl->file().get());
2710 e.replace ("DCP Constraints Profile", "Fred");
2713 check_verify_result (
2716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2722 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2724 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2725 auto dcp = make_simple (dir);
2728 auto const cpl = dcp->cpls()[0];
2731 Editor e (cpl->file().get());
2732 e.replace ("<meta:Value>", "<meta:ValueX>");
2733 e.replace ("</meta:Value>", "</meta:ValueX>");
2736 check_verify_result (
2739 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2740 { 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 },
2741 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2746 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2748 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2749 auto dcp = make_simple (dir);
2752 auto const cpl = dcp->cpls()[0];
2755 Editor e (cpl->file().get());
2756 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2759 check_verify_result (
2762 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2763 { 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() },
2768 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2770 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2771 auto dcp = make_simple (dir);
2774 auto const cpl = dcp->cpls()[0];
2777 Editor e (cpl->file().get());
2778 e.replace ("<meta:Property>", "<meta:PropertyX>");
2779 e.replace ("</meta:Property>", "</meta:PropertyX>");
2782 check_verify_result (
2785 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2786 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2787 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2792 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2794 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2795 auto dcp = make_simple (dir);
2798 auto const cpl = dcp->cpls()[0];
2801 Editor e (cpl->file().get());
2802 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2803 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2806 check_verify_result (
2809 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2810 { 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 },
2811 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2817 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2819 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2820 prepare_directory (dir);
2821 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2822 copy_file (i.path(), dir / i.path().filename());
2825 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2826 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2830 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2833 check_verify_result (
2836 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2837 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2838 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2839 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2840 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2841 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2842 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2843 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2848 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2850 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2851 prepare_directory (dir);
2852 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2853 copy_file (i.path(), dir / i.path().filename());
2856 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2857 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2860 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2863 check_verify_result (
2866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2868 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2869 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2870 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2871 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2872 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2877 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2879 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2880 prepare_directory (dir);
2881 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2882 copy_file (i.path(), dir / i.path().filename());
2886 Editor e (dir / dcp_test1_pkl);
2887 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2890 check_verify_result ({dir}, {});
2894 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2896 path dir ("build/test/verify_must_not_be_partially_encrypted");
2897 prepare_directory (dir);
2901 auto signer = make_shared<dcp::CertificateChain>();
2902 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2903 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2904 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2905 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2907 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2911 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2914 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
2915 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2916 for (int i = 0; i < 24; ++i) {
2917 writer->write (j2c.data(), j2c.size());
2919 writer->finalize ();
2921 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2923 auto reel = make_shared<dcp::Reel>(
2924 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2925 make_shared<dcp::ReelSoundAsset>(ms, 0)
2928 reel->add (simple_markers());
2932 cpl->set_content_version (
2933 {"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"}
2935 cpl->set_annotation_text ("A Test DCP");
2936 cpl->set_issuer ("OpenDCP 0.0.25");
2937 cpl->set_creator ("OpenDCP 0.0.25");
2938 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2939 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
2940 cpl->set_main_sound_sample_rate (48000);
2941 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2942 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2943 cpl->set_version_number (1);
2947 d.set_issuer("OpenDCP 0.0.25");
2948 d.set_creator("OpenDCP 0.0.25");
2949 d.set_issue_date("2012-07-17T04:45:18+00:00");
2950 d.set_annotation_text("A Test DCP");
2951 d.write_xml(signer);
2953 check_verify_result (
2956 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2961 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2963 vector<dcp::VerificationNote> notes;
2964 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"));
2965 auto reader = picture.start_read ();
2966 auto frame = reader->get_frame (0);
2967 verify_j2k(frame, 0, 24, notes);
2968 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2972 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2974 vector<dcp::VerificationNote> notes;
2975 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
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_libdcp)
2985 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2986 prepare_directory (dir);
2987 auto dcp = make_simple (dir);
2989 vector<dcp::VerificationNote> notes;
2990 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2991 auto reader = picture.start_read ();
2992 auto frame = reader->get_frame (0);
2993 verify_j2k(frame, 0, 24, notes);
2994 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2998 /** Check that ResourceID and the XML ID being different is spotted */
2999 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3001 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3002 prepare_directory (dir);
3004 ASDCP::WriterInfo writer_info;
3005 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3008 auto mxf_id = dcp::make_uuid ();
3009 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3010 BOOST_REQUIRE (c == Kumu::UUID_Length);
3012 auto resource_id = dcp::make_uuid ();
3013 ASDCP::TimedText::TimedTextDescriptor descriptor;
3014 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3015 DCP_ASSERT (c == Kumu::UUID_Length);
3017 auto xml_id = dcp::make_uuid ();
3018 ASDCP::TimedText::MXFWriter writer;
3019 auto subs_mxf = dir / "subs.mxf";
3020 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3021 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3022 writer.WriteTimedTextResource (dcp::String::compose(
3023 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3024 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3025 "<Id>urn:uuid:%1</Id>"
3026 "<ContentTitleText>Content</ContentTitleText>"
3027 "<AnnotationText>Annotation</AnnotationText>"
3028 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3029 "<ReelNumber>1</ReelNumber>"
3030 "<Language>en-US</Language>"
3031 "<EditRate>25 1</EditRate>"
3032 "<TimeCodeRate>25</TimeCodeRate>"
3033 "<StartTime>00:00:00:00</StartTime>"
3034 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3036 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3037 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3038 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3047 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3048 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3050 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3052 check_verify_result (
3055 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3056 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3057 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3063 /** Check that ResourceID and the MXF ID being the same is spotted */
3064 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3066 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3067 prepare_directory (dir);
3069 ASDCP::WriterInfo writer_info;
3070 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3073 auto mxf_id = dcp::make_uuid ();
3074 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3075 BOOST_REQUIRE (c == Kumu::UUID_Length);
3077 auto resource_id = mxf_id;
3078 ASDCP::TimedText::TimedTextDescriptor descriptor;
3079 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3080 DCP_ASSERT (c == Kumu::UUID_Length);
3082 auto xml_id = resource_id;
3083 ASDCP::TimedText::MXFWriter writer;
3084 auto subs_mxf = dir / "subs.mxf";
3085 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3086 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3087 writer.WriteTimedTextResource (dcp::String::compose(
3088 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3089 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3090 "<Id>urn:uuid:%1</Id>"
3091 "<ContentTitleText>Content</ContentTitleText>"
3092 "<AnnotationText>Annotation</AnnotationText>"
3093 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3094 "<ReelNumber>1</ReelNumber>"
3095 "<Language>en-US</Language>"
3096 "<EditRate>25 1</EditRate>"
3097 "<TimeCodeRate>25</TimeCodeRate>"
3098 "<StartTime>00:00:00:00</StartTime>"
3099 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3101 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3102 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3103 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3112 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3113 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3115 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3117 check_verify_result (
3120 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3121 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3122 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3123 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3124 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3129 /** Check a DCP with a 3D asset marked as 2D */
3130 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3132 check_verify_result (
3133 { private_test / "data" / "xm" },
3136 dcp::VerificationNote::Type::WARNING,
3137 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3140 dcp::VerificationNote::Type::BV21_ERROR,
3141 dcp::VerificationNote::Code::INVALID_STANDARD
3148 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3150 path dir = "build/test/verify_unexpected_things_in_main_markers";
3151 prepare_directory (dir);
3152 auto dcp = make_simple (dir, 1, 24);
3156 Editor e (find_cpl(dir));
3158 " <IntrinsicDuration>24</IntrinsicDuration>",
3159 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3163 dcp::CPL cpl (find_cpl(dir));
3165 check_verify_result (
3168 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3169 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3170 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3175 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3177 path dir = "build/test/verify_invalid_content_kind";
3178 prepare_directory (dir);
3179 auto dcp = make_simple (dir, 1, 24);
3183 Editor e(find_cpl(dir));
3184 e.replace("trailer", "trip");
3187 dcp::CPL cpl (find_cpl(dir));
3189 check_verify_result (
3192 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3193 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3199 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3201 path dir = "build/test/verify_valid_content_kind";
3202 prepare_directory (dir);
3203 auto dcp = make_simple (dir, 1, 24);
3207 Editor e(find_cpl(dir));
3208 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3211 dcp::CPL cpl (find_cpl(dir));
3213 check_verify_result (
3216 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3222 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3224 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3225 prepare_directory(dir);
3226 auto dcp = make_simple(dir, 1, 24);
3229 auto constexpr area = "<meta:MainPictureActiveArea>";
3232 Editor e(find_cpl(dir));
3233 e.delete_lines_after(area, 2);
3234 e.insert(area, "<meta:Height>4080</meta:Height>");
3235 e.insert(area, "<meta:Width>1997</meta:Width>");
3238 dcp::PKL pkl(find_pkl(dir));
3239 dcp::CPL cpl(find_cpl(dir));
3241 check_verify_result(
3244 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3245 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3246 { 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)) },
3251 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3253 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3254 prepare_directory(dir);
3255 auto dcp = make_simple(dir, 1, 24);
3258 auto constexpr area = "<meta:MainPictureActiveArea>";
3261 Editor e(find_cpl(dir));
3262 e.delete_lines_after(area, 2);
3263 e.insert(area, "<meta:Height>5125</meta:Height>");
3264 e.insert(area, "<meta:Width>9900</meta:Width>");
3267 dcp::PKL pkl(find_pkl(dir));
3268 dcp::CPL cpl(find_cpl(dir));
3270 check_verify_result(
3273 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3274 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3275 { 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)) },
3276 { 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)) },
3281 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3285 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3286 prepare_directory(dir);
3287 auto dcp = make_simple(dir, 1, 24);
3291 Editor e(find_pkl(dir));
3292 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3295 dcp::PKL pkl(find_pkl(dir));
3297 check_verify_result(
3300 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3305 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3309 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3310 prepare_directory(dir);
3311 auto dcp = make_simple(dir, 1, 24);
3315 Editor e(find_asset_map(dir));
3316 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3319 dcp::PKL pkl(find_pkl(dir));
3320 dcp::AssetMap asset_map(find_asset_map(dir));
3322 check_verify_result(
3325 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3326 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3331 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3333 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3335 dcp::MXFMetadata mxf_meta;
3336 mxf_meta.company_name = "OpenDCP";
3337 mxf_meta.product_name = "OpenDCP";
3338 mxf_meta.product_version = "0.0.25";
3340 auto constexpr sample_rate = 48000;
3341 auto constexpr frames = 240;
3343 boost::filesystem::remove_all(path);
3344 boost::filesystem::create_directories(path);
3345 auto dcp = make_shared<dcp::DCP>(path);
3346 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3347 cpl->set_annotation_text("hello");
3348 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3349 cpl->set_main_sound_sample_rate(sample_rate);
3350 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3351 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3352 cpl->set_version_number(1);
3356 /* Reel with 2 channels of audio */
3358 auto mp = simple_picture(path, "1", frames, {});
3359 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3361 auto reel = make_shared<dcp::Reel>(
3362 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3363 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3366 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3367 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3374 /* Reel with 6 channels of audio */
3376 auto mp = simple_picture(path, "2", frames, {});
3377 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3379 auto reel = make_shared<dcp::Reel>(
3380 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3381 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3384 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3385 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3392 dcp->set_annotation_text("hello");
3395 check_verify_result(
3398 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3403 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3405 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3407 dcp::MXFMetadata mxf_meta;
3408 mxf_meta.company_name = "OpenDCP";
3409 mxf_meta.product_name = "OpenDCP";
3410 mxf_meta.product_version = "0.0.25";
3412 auto constexpr sample_rate = 48000;
3413 auto constexpr frames = 240;
3415 boost::filesystem::remove_all(path);
3416 boost::filesystem::create_directories(path);
3417 auto dcp = make_shared<dcp::DCP>(path);
3418 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3419 cpl->set_annotation_text("hello");
3420 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3421 cpl->set_main_sound_sample_rate(sample_rate);
3422 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3423 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3424 cpl->set_version_number(1);
3426 auto mp = simple_picture(path, "1", frames, {});
3427 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3429 auto reel = make_shared<dcp::Reel>(
3430 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3431 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3434 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3435 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3436 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3442 dcp->set_annotation_text("hello");
3445 check_verify_result(
3448 { 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)) },
3453 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3455 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3456 auto constexpr video_frames = 24;
3457 auto constexpr sample_rate = 48000;
3459 boost::filesystem::remove_all(path);
3460 boost::filesystem::create_directories(path);
3462 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3463 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3465 dcp::Size const size(1998, 1080);
3466 auto image = make_shared<dcp::OpenJPEGImage>(size);
3467 boost::random::mt19937 rng(1);
3468 boost::random::uniform_int_distribution<> dist(0, 4095);
3469 for (int c = 0; c < 3; ++c) {
3470 for (int p = 0; p < (1998 * 1080); ++p) {
3471 image->data(c)[p] = dist(rng);
3474 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3475 for (int i = 0; i < 24; ++i) {
3476 picture_writer->write(j2c.data(), j2c.size());
3478 picture_writer->finalize();
3480 auto dcp = make_shared<dcp::DCP>(path);
3481 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3482 cpl->set_content_version(
3483 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3485 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3486 cpl->set_main_sound_sample_rate(sample_rate);
3487 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3488 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3489 cpl->set_version_number(1);
3491 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3493 auto reel = make_shared<dcp::Reel>(
3494 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3495 make_shared<dcp::ReelSoundAsset>(ms, 0)
3500 dcp->set_annotation_text("A Test DCP");
3503 check_verify_result(
3506 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3507 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3508 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3509 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3514 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3516 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3517 check_verify_result(
3520 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3521 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3522 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3523 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3524 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3525 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3530 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3532 path const dir("build/test/verify_missing_load_font");
3533 prepare_directory (dir);
3534 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3536 Editor editor(dir / "subs.xml");
3537 editor.delete_first_line_containing("LoadFont");
3539 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3540 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3541 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3543 check_verify_result (
3545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3546 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3552 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3554 boost::filesystem::path const dir = dcp::String::compose("build/test/%1", boost::unit_test::framework::current_test_case().full_name());
3555 prepare_directory(dir);
3556 auto dcp = make_simple (dir, 1, 202);
3559 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3560 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3561 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3562 "<ContentTitleText>Content</ContentTitleText>"
3563 "<AnnotationText>Annotation</AnnotationText>"
3564 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3565 "<ReelNumber>1</ReelNumber>"
3566 "<EditRate>24 1</EditRate>"
3567 "<TimeCodeRate>24</TimeCodeRate>"
3568 "<StartTime>00:00:00:00</StartTime>"
3569 "<Language>de-DE</Language>"
3571 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3572 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3573 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3579 dcp::File xml_file(dir / "subs.xml", "w");
3580 BOOST_REQUIRE(xml_file);
3581 xml_file.write(xml.c_str(), xml.size(), 1);
3583 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3584 subs->write(dir / "subs.mxf");
3586 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3587 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3590 check_verify_result (
3593 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3598 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3600 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3601 boost::filesystem::remove_all(dir);
3603 auto dcp1 = make_simple(dir / "1");
3606 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3608 auto dcp2 = make_simple(dir / "2");
3610 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3612 boost::filesystem::remove(dir / "1" / "video.mxf");
3613 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3615 check_verify_result(
3618 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)