2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
70 using std::make_shared;
72 using std::shared_ptr;
75 using boost::optional;
76 using namespace boost::filesystem;
79 static list<pair<string, optional<path>>> stages;
81 static string filename_to_id(boost::filesystem::path path)
83 return path.string().substr(4, path.string().length() - 8);
87 boost::filesystem::path
90 return find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
97 return filename_to_id(dcp_test1_pkl());
101 boost::filesystem::path
104 return find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
111 return filename_to_id(dcp_test1_cpl());
114 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
118 encryption_test_cpl_id()
120 return filename_to_id(find_file("test/ref/DCP/encryption_test", "cpl_").filename());
125 encryption_test_pkl_id()
127 return filename_to_id(find_file("test/ref/DCP/encryption_test", "pkl_").filename());
131 stage (string s, optional<path> p)
133 stages.push_back (make_pair (s, p));
143 prepare_directory (path path)
145 using namespace boost::filesystem;
147 create_directories (path);
151 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
152 * to make a new sacrificial test DCP.
155 setup (int reference_number, string verify_test_suffix)
157 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
158 prepare_directory (dir);
159 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
160 copy_file (i.path(), dir / i.path().filename());
169 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
171 auto reel = make_shared<dcp::Reel>();
172 reel->add (reel_asset);
173 reel->add (simple_markers());
175 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
177 auto dcp = make_shared<dcp::DCP>(dir);
179 dcp->set_annotation_text("hello");
186 LIBDCP_DISABLE_WARNINGS
189 dump_notes (vector<dcp::VerificationNote> const & notes)
191 for (auto i: notes) {
192 std::cout << dcp::note_to_string(i) << "\n";
195 LIBDCP_ENABLE_WARNINGS
200 check_verify_result(vector<path> dir, vector<dcp::DecryptedKDM> kdm, vector<dcp::VerificationNote> test_notes)
202 auto notes = dcp::verify({dir}, kdm, &stage, &progress, {}, xsd_test);
203 std::sort (notes.begin(), notes.end());
204 std::sort (test_notes.begin(), test_notes.end());
206 string message = "\nVerification notes from test:\n";
207 for (auto i: notes) {
208 message += " " + note_to_string(i) + "\n";
209 message += dcp::String::compose(
210 " [%1 %2 %3 %4 %5]\n",
211 static_cast<int>(i.type()),
212 static_cast<int>(i.code()),
213 i.note().get_value_or("<none>"),
214 i.file().get_value_or("<none>"),
215 i.line().get_value_or(0)
218 message += "Expected:\n";
219 for (auto i: test_notes) {
220 message += " " + note_to_string(i) + "\n";
221 message += dcp::String::compose(
222 " [%1 %2 %3 %4 %5]\n",
223 static_cast<int>(i.type()),
224 static_cast<int>(i.code()),
225 i.note().get_value_or("<none>"),
226 i.file().get_value_or("<none>"),
227 i.line().get_value_or(0)
231 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
235 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
236 * replacing from with to. Verify the resulting DCP and check that the results match the given
241 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
243 auto dir = setup (1, suffix);
246 Editor e (file(suffix));
247 e.replace (from, to);
250 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
252 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
253 auto i = notes.begin();
254 auto j = codes.begin();
255 while (i != notes.end()) {
256 BOOST_CHECK_EQUAL (i->code(), *j);
265 add_font(shared_ptr<dcp::SubtitleAsset> asset)
267 dcp::ArrayData fake_font(1024);
268 asset->add_font("font", fake_font);
272 BOOST_AUTO_TEST_CASE (verify_no_error)
275 auto dir = setup (1, "no_error");
276 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
278 path const cpl_file = dir / dcp_test1_cpl();
279 path const pkl_file = dir / dcp_test1_pkl();
280 path const assetmap_file = dir / "ASSETMAP.xml";
282 auto st = stages.begin();
283 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
284 BOOST_REQUIRE (st->second);
285 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
287 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
288 BOOST_REQUIRE (st->second);
289 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
291 BOOST_CHECK_EQUAL (st->first, "Checking reel");
292 BOOST_REQUIRE (!st->second);
294 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
295 BOOST_REQUIRE (st->second);
296 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
298 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
299 BOOST_REQUIRE (st->second);
300 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
302 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
303 BOOST_REQUIRE (st->second);
304 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
306 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
307 BOOST_REQUIRE (st->second);
308 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
310 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
311 BOOST_REQUIRE (st->second);
312 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
314 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
315 BOOST_REQUIRE (st->second);
316 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
318 BOOST_REQUIRE (st == stages.end());
320 BOOST_CHECK_EQUAL (notes.size(), 0U);
324 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
326 using namespace boost::filesystem;
328 auto dir = setup (1, "incorrect_picture_sound_hash");
330 auto video_path = path(dir / "video.mxf");
331 auto mod = fopen(video_path.string().c_str(), "r+b");
333 fseek (mod, 4096, SEEK_SET);
335 fwrite (&x, sizeof(x), 1, mod);
338 auto audio_path = path(dir / "audio.mxf");
339 mod = fopen(audio_path.string().c_str(), "r+b");
341 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
342 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
345 dcp::ASDCPErrorSuspender sus;
346 check_verify_result (
350 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
351 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
356 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
358 using namespace boost::filesystem;
360 auto dir = setup (1, "mismatched_picture_sound_hashes");
363 Editor e (dir / dcp_test1_pkl());
364 e.replace ("<Hash>", "<Hash>x");
367 check_verify_result (
371 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl()) },
372 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
373 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
374 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
375 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 12 },
376 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
381 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
383 auto dir = setup (1, "failed_read_content_kind");
386 Editor e (dir / dcp_test1_cpl());
387 e.replace ("<ContentKind>", "<ContentKind>x");
390 check_verify_result (
394 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl()) },
395 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
404 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
412 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
418 asset_map (string suffix)
420 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
424 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
426 check_verify_result_after_replace (
427 "invalid_picture_frame_rate", &cpl,
428 "<FrameRate>24 1", "<FrameRate>99 1",
429 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
430 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
434 BOOST_AUTO_TEST_CASE (verify_missing_asset)
436 auto dir = setup (1, "missing_asset");
437 remove (dir / "video.mxf");
438 check_verify_result (
442 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
447 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
449 check_verify_result_after_replace (
450 "empty_asset_path", &asset_map,
451 "<Path>video.mxf</Path>", "<Path></Path>",
452 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
457 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
459 check_verify_result_after_replace (
460 "mismatched_standard", &cpl,
461 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
462 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
463 dcp::VerificationNote::Code::INVALID_XML,
464 dcp::VerificationNote::Code::INVALID_XML,
465 dcp::VerificationNote::Code::INVALID_XML,
466 dcp::VerificationNote::Code::INVALID_XML,
467 dcp::VerificationNote::Code::INVALID_XML,
468 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
473 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
475 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
476 check_verify_result_after_replace (
477 "invalid_xml_cpl_id", &cpl,
478 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
479 { dcp::VerificationNote::Code::INVALID_XML }
484 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
486 check_verify_result_after_replace (
487 "invalid_xml_issue_date", &cpl,
488 "<IssueDate>", "<IssueDate>x",
489 { dcp::VerificationNote::Code::INVALID_XML,
490 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
495 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
497 check_verify_result_after_replace (
498 "invalid_xml_pkl_id", &pkl,
499 "<Id>urn:uuid:" + dcp_test1_pkl_id().substr(0, 3),
500 "<Id>urn:uuid:x" + dcp_test1_pkl_id().substr(1, 2),
501 { dcp::VerificationNote::Code::INVALID_XML }
506 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
508 check_verify_result_after_replace (
509 "invalid_xml_asset_map_id", &asset_map,
510 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
511 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
512 { dcp::VerificationNote::Code::INVALID_XML }
517 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
520 auto dir = setup (3, "verify_invalid_standard");
521 auto notes = dcp::verify({dir}, {}, &stage, &progress, {}, xsd_test);
523 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
524 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
525 path const assetmap_file = dir / "ASSETMAP";
527 auto st = stages.begin();
528 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
529 BOOST_REQUIRE (st->second);
530 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
532 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
533 BOOST_REQUIRE (st->second);
534 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
536 BOOST_CHECK_EQUAL (st->first, "Checking reel");
537 BOOST_REQUIRE (!st->second);
539 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
540 BOOST_REQUIRE (st->second);
541 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
543 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
544 BOOST_REQUIRE (st->second);
545 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
547 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
548 BOOST_REQUIRE (st->second);
549 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
551 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
552 BOOST_REQUIRE (st->second);
553 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
555 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
556 BOOST_REQUIRE (st->second);
557 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
559 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
560 BOOST_REQUIRE (st->second);
561 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
563 BOOST_REQUIRE (st == stages.end());
565 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
566 auto i = notes.begin ();
567 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
568 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
570 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
571 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
574 /* DCP with a short asset */
575 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
577 auto dir = setup (8, "invalid_duration");
581 BOOST_REQUIRE(dcp.cpls().size() == 1);
582 auto cpl = dcp.cpls()[0];
584 check_verify_result (
588 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
589 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
590 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
591 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
592 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
593 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") },
594 dcp::VerificationNote(
595 dcp::VerificationNote::Type::WARNING,
596 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
598 ).set_id("d74fda30-d5f4-4c5f-870f-ebc089d97eb7")
605 dcp_from_frame (dcp::ArrayData const& frame, path dir)
607 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
608 create_directories (dir);
609 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
610 for (int i = 0; i < 24; ++i) {
611 writer->write (frame.data(), frame.size());
615 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
616 return write_dcp_with_single_asset (dir, reel_asset);
620 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
622 int const too_big = 1302083 * 2;
624 /* Compress a black image */
625 auto image = black_image ();
626 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
627 BOOST_REQUIRE (frame.size() < too_big);
629 /* Place it in a bigger block with some zero padding at the end */
630 dcp::ArrayData oversized_frame(too_big);
631 memcpy (oversized_frame.data(), frame.data(), frame.size());
632 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
634 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
635 prepare_directory (dir);
636 auto cpl = dcp_from_frame (oversized_frame, dir);
638 check_verify_result (
642 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
643 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
644 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
649 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
651 int const nearly_too_big = 1302083 * 0.98;
653 /* Compress a black image */
654 auto image = black_image ();
655 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
656 BOOST_REQUIRE (frame.size() < nearly_too_big);
658 /* Place it in a bigger block with some zero padding at the end */
659 dcp::ArrayData oversized_frame(nearly_too_big);
660 memcpy (oversized_frame.data(), frame.data(), frame.size());
661 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
663 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
664 prepare_directory (dir);
665 auto cpl = dcp_from_frame (oversized_frame, dir);
667 check_verify_result (
671 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
672 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
673 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
678 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
680 /* Compress a black image */
681 auto image = black_image ();
682 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
683 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
685 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
686 prepare_directory (dir);
687 auto cpl = dcp_from_frame (frame, dir);
689 check_verify_result({ dir }, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
693 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
695 path const dir("build/test/verify_valid_interop_subtitles");
696 prepare_directory (dir);
697 copy_file ("test/data/subs1.xml", dir / "subs.xml");
698 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
699 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
700 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
702 check_verify_result (
706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
707 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
712 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
714 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
715 prepare_directory(dir);
716 copy_file("test/data/subs1.xml", dir / "ccap.xml");
717 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
718 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
719 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
721 check_verify_result (
725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
726 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
731 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
733 using namespace boost::filesystem;
735 path const dir("build/test/verify_invalid_interop_subtitles");
736 prepare_directory (dir);
737 copy_file ("test/data/subs1.xml", dir / "subs.xml");
738 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
739 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
740 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
743 Editor e (dir / "subs.xml");
744 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
747 check_verify_result (
751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
754 dcp::VerificationNote::Type::ERROR,
755 dcp::VerificationNote::Code::INVALID_XML,
756 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
760 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
765 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
767 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
768 prepare_directory(dir);
769 copy_file("test/data/subs4.xml", dir / "subs.xml");
770 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
771 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
772 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
774 check_verify_result (
778 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
779 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
780 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
786 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
788 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
789 prepare_directory(dir);
790 copy_file("test/data/subs5.xml", dir / "subs.xml");
791 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
792 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
793 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
795 check_verify_result (
799 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
800 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
806 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
808 path const dir("build/test/verify_valid_smpte_subtitles");
809 prepare_directory (dir);
810 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
811 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
812 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
813 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
820 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
821 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
826 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
828 using namespace boost::filesystem;
830 path const dir("build/test/verify_invalid_smpte_subtitles");
831 prepare_directory (dir);
832 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
833 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
834 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
835 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
836 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
838 check_verify_result (
842 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
844 dcp::VerificationNote::Type::ERROR,
845 dcp::VerificationNote::Code::INVALID_XML,
846 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
851 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
852 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
853 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
858 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
860 path const dir("build/test/verify_empty_text_node_in_subtitles");
861 prepare_directory (dir);
862 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
863 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
864 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
865 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
867 check_verify_result (
871 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
872 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
873 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
874 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
875 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
876 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
881 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
882 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
884 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
885 prepare_directory (dir);
886 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
887 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
888 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
889 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
891 check_verify_result (
895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
896 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
901 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
902 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
904 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
905 prepare_directory (dir);
906 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
907 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
908 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
909 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
911 check_verify_result (
915 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
917 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
918 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
923 BOOST_AUTO_TEST_CASE (verify_external_asset)
925 path const ov_dir("build/test/verify_external_asset");
926 prepare_directory (ov_dir);
928 auto image = black_image ();
929 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
930 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
931 dcp_from_frame (frame, ov_dir);
933 dcp::DCP ov (ov_dir);
936 path const vf_dir("build/test/verify_external_asset_vf");
937 prepare_directory (vf_dir);
939 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
940 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
942 check_verify_result (
946 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
952 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
954 path const dir("build/test/verify_valid_cpl_metadata");
955 prepare_directory (dir);
957 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
958 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
959 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
961 auto reel = make_shared<dcp::Reel>();
962 reel->add (reel_asset);
964 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
965 reel->add (simple_markers(16 * 24));
967 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
969 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
970 cpl->set_main_sound_sample_rate (48000);
971 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
972 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
973 cpl->set_version_number (1);
977 dcp.set_annotation_text("hello");
983 find_prefix(path dir, string prefix)
985 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
986 return boost::starts_with(p.filename().string(), prefix);
989 BOOST_REQUIRE(iter != directory_iterator());
994 path find_cpl (path dir)
996 return find_prefix(dir, "cpl_");
1003 return find_prefix(dir, "pkl_");
1008 find_asset_map(path dir)
1010 return find_prefix(dir, "ASSETMAP");
1014 /* DCP with invalid CompositionMetadataAsset */
1015 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1017 using namespace boost::filesystem;
1019 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1020 prepare_directory (dir);
1022 auto reel = make_shared<dcp::Reel>();
1023 reel->add (black_picture_asset(dir));
1024 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1026 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1027 cpl->set_main_sound_sample_rate (48000);
1028 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1029 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1030 cpl->set_version_number (1);
1032 reel->add (simple_markers());
1036 dcp.set_annotation_text("hello");
1040 Editor e (find_cpl(dir));
1041 e.replace ("MainSound", "MainSoundX");
1044 check_verify_result (
1048 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1049 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1051 dcp::VerificationNote::Type::ERROR,
1052 dcp::VerificationNote::Code::INVALID_XML,
1053 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1054 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1055 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1056 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1057 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1058 "ExtensionMetadataList?,)'"),
1059 canonical(cpl->file().get()),
1062 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1067 /* DCP with invalid CompositionMetadataAsset */
1068 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1070 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1071 prepare_directory (dir);
1073 auto reel = make_shared<dcp::Reel>();
1074 reel->add (black_picture_asset(dir));
1075 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1077 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1078 cpl->set_main_sound_sample_rate (48000);
1079 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1080 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1084 dcp.set_annotation_text("hello");
1088 Editor e (find_cpl(dir));
1089 e.replace ("meta:Width", "meta:WidthX");
1092 check_verify_result (
1095 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1100 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1102 path const dir("build/test/verify_invalid_language1");
1103 prepare_directory (dir);
1104 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1105 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1106 asset->_language = "wrong-andbad";
1107 asset->write (dir / "subs.mxf");
1108 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1109 reel_asset->_language = "badlang";
1110 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1112 check_verify_result (
1116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1123 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1124 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1126 path const dir("build/test/verify_invalid_language2");
1127 prepare_directory (dir);
1128 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1129 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1130 asset->_language = "wrong-andbad";
1131 asset->write (dir / "subs.mxf");
1132 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1133 reel_asset->_language = "badlang";
1134 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1136 check_verify_result (
1140 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1141 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1147 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1148 * the release territory.
1150 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1152 path const dir("build/test/verify_invalid_language3");
1153 prepare_directory (dir);
1155 auto picture = simple_picture (dir, "foo");
1156 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1157 auto reel = make_shared<dcp::Reel>();
1158 reel->add (reel_picture);
1159 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1160 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1161 reel->add (reel_sound);
1162 reel->add (simple_markers());
1164 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1166 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1167 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1168 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1169 cpl->set_main_sound_sample_rate (48000);
1170 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1171 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1172 cpl->set_version_number (1);
1173 cpl->_release_territory = "fred-jim";
1174 auto dcp = make_shared<dcp::DCP>(dir);
1176 dcp->set_annotation_text("hello");
1179 check_verify_result (
1183 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1185 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1186 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1192 vector<dcp::VerificationNote>
1193 check_picture_size (int width, int height, int frame_rate, bool three_d)
1195 using namespace boost::filesystem;
1197 path dcp_path = "build/test/verify_picture_test";
1198 prepare_directory (dcp_path);
1200 shared_ptr<dcp::PictureAsset> mp;
1202 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1204 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1206 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1208 auto image = black_image (dcp::Size(width, height));
1209 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1210 int const length = three_d ? frame_rate * 2 : frame_rate;
1211 for (int i = 0; i < length; ++i) {
1212 picture_writer->write (j2c.data(), j2c.size());
1214 picture_writer->finalize ();
1216 auto d = make_shared<dcp::DCP>(dcp_path);
1217 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1218 cpl->set_annotation_text ("A Test DCP");
1219 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1220 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1221 cpl->set_main_sound_sample_rate (48000);
1222 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1223 cpl->set_main_picture_active_area(dcp::Size(width, height));
1224 cpl->set_version_number (1);
1226 auto reel = make_shared<dcp::Reel>();
1229 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1231 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1234 reel->add (simple_markers(frame_rate));
1239 d->set_annotation_text("A Test DCP");
1242 return dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test);
1248 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1250 auto notes = check_picture_size(width, height, frame_rate, three_d);
1251 BOOST_CHECK_EQUAL (notes.size(), 0U);
1257 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1259 auto notes = check_picture_size(width, height, frame_rate, three_d);
1260 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1261 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1262 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1268 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1270 auto notes = check_picture_size(width, height, frame_rate, three_d);
1271 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1272 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1273 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1279 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1281 auto notes = check_picture_size(width, height, frame_rate, three_d);
1282 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1283 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1284 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1288 BOOST_AUTO_TEST_CASE (verify_picture_size)
1290 using namespace boost::filesystem;
1293 check_picture_size_ok (2048, 858, 24, false);
1294 check_picture_size_ok (2048, 858, 25, false);
1295 check_picture_size_ok (2048, 858, 48, false);
1296 check_picture_size_ok (2048, 858, 24, true);
1297 check_picture_size_ok (2048, 858, 25, true);
1298 check_picture_size_ok (2048, 858, 48, true);
1301 check_picture_size_ok (1998, 1080, 24, false);
1302 check_picture_size_ok (1998, 1080, 25, false);
1303 check_picture_size_ok (1998, 1080, 48, false);
1304 check_picture_size_ok (1998, 1080, 24, true);
1305 check_picture_size_ok (1998, 1080, 25, true);
1306 check_picture_size_ok (1998, 1080, 48, true);
1309 check_picture_size_ok (4096, 1716, 24, false);
1312 check_picture_size_ok (3996, 2160, 24, false);
1314 /* Bad frame size */
1315 check_picture_size_bad_frame_size (2050, 858, 24, false);
1316 check_picture_size_bad_frame_size (2048, 658, 25, false);
1317 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1318 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1320 /* Bad 2K frame rate */
1321 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1322 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1323 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1325 /* Bad 4K frame rate */
1326 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1327 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1330 auto notes = check_picture_size(3996, 2160, 24, true);
1331 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1332 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1333 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1339 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")
1342 std::make_shared<dcp::SubtitleString>(
1350 dcp::Time(start_frame, 24, 24),
1351 dcp::Time(end_frame, 24, 24),
1353 dcp::HAlign::CENTER,
1357 dcp::Direction::LTR,
1364 std::vector<dcp::Ruby>()
1370 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1372 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1373 prepare_directory (dir);
1375 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1376 for (int i = 0; i < 2048; ++i) {
1377 add_test_subtitle (asset, i * 24, i * 24 + 20);
1380 asset->set_language (dcp::LanguageTag("de-DE"));
1381 asset->write (dir / "subs.mxf");
1382 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1383 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1385 check_verify_result (
1389 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1391 dcp::VerificationNote::Type::BV21_ERROR,
1392 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1394 canonical(dir / "subs.mxf")
1396 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1397 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1403 shared_ptr<dcp::SMPTESubtitleAsset>
1404 make_large_subtitle_asset (path font_file)
1406 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1407 dcp::ArrayData big_fake_font(1024 * 1024);
1408 big_fake_font.write (font_file);
1409 for (int i = 0; i < 116; ++i) {
1410 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1418 verify_timed_text_asset_too_large (string name)
1420 auto const dir = path("build/test") / name;
1421 prepare_directory (dir);
1422 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1423 add_test_subtitle (asset, 0, 240);
1424 asset->set_language (dcp::LanguageTag("de-DE"));
1425 asset->write (dir / "subs.mxf");
1427 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1428 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1430 check_verify_result (
1434 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1435 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1436 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1437 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1438 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1443 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1445 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1446 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1450 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1452 path dir = "build/test/verify_missing_subtitle_language";
1453 prepare_directory (dir);
1454 auto dcp = make_simple (dir, 1, 106);
1457 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1458 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1459 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1460 "<ContentTitleText>Content</ContentTitleText>"
1461 "<AnnotationText>Annotation</AnnotationText>"
1462 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1463 "<ReelNumber>1</ReelNumber>"
1464 "<EditRate>24 1</EditRate>"
1465 "<TimeCodeRate>24</TimeCodeRate>"
1466 "<StartTime>00:00:00:00</StartTime>"
1467 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1469 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1470 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1471 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1477 dcp::File xml_file(dir / "subs.xml", "w");
1478 BOOST_REQUIRE (xml_file);
1479 xml_file.write(xml.c_str(), xml.size(), 1);
1481 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1482 subs->write (dir / "subs.mxf");
1484 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1485 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1488 check_verify_result (
1492 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1493 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1498 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1500 path path ("build/test/verify_mismatched_subtitle_languages");
1501 auto constexpr reel_length = 192;
1502 auto dcp = make_simple (path, 2, reel_length);
1503 auto cpl = dcp->cpls()[0];
1506 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1507 subs->set_language (dcp::LanguageTag("de-DE"));
1508 subs->add (simple_subtitle());
1510 subs->write (path / "subs1.mxf");
1511 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1512 cpl->reels()[0]->add(reel_subs);
1516 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1517 subs->set_language (dcp::LanguageTag("en-US"));
1518 subs->add (simple_subtitle());
1520 subs->write (path / "subs2.mxf");
1521 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1522 cpl->reels()[1]->add(reel_subs);
1527 check_verify_result (
1531 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1532 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1533 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1538 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1540 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1541 auto constexpr reel_length = 192;
1542 auto dcp = make_simple (path, 2, reel_length);
1543 auto cpl = dcp->cpls()[0];
1546 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1547 ccaps->set_language (dcp::LanguageTag("de-DE"));
1548 ccaps->add (simple_subtitle());
1550 ccaps->write (path / "subs1.mxf");
1551 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1552 cpl->reels()[0]->add(reel_ccaps);
1556 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1557 ccaps->set_language (dcp::LanguageTag("en-US"));
1558 ccaps->add (simple_subtitle());
1560 ccaps->write (path / "subs2.mxf");
1561 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1562 cpl->reels()[1]->add(reel_ccaps);
1567 check_verify_result (
1571 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1572 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1577 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1579 path dir = "build/test/verify_missing_subtitle_start_time";
1580 prepare_directory (dir);
1581 auto dcp = make_simple (dir, 1, 106);
1584 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1585 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1586 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1587 "<ContentTitleText>Content</ContentTitleText>"
1588 "<AnnotationText>Annotation</AnnotationText>"
1589 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1590 "<ReelNumber>1</ReelNumber>"
1591 "<Language>de-DE</Language>"
1592 "<EditRate>24 1</EditRate>"
1593 "<TimeCodeRate>24</TimeCodeRate>"
1594 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1596 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1597 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1598 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1604 dcp::File xml_file(dir / "subs.xml", "w");
1605 BOOST_REQUIRE (xml_file);
1606 xml_file.write(xml.c_str(), xml.size(), 1);
1608 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1609 subs->write (dir / "subs.mxf");
1611 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1612 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1615 check_verify_result (
1619 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1620 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1625 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1627 path dir = "build/test/verify_invalid_subtitle_start_time";
1628 prepare_directory (dir);
1629 auto dcp = make_simple (dir, 1, 106);
1632 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1633 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1634 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1635 "<ContentTitleText>Content</ContentTitleText>"
1636 "<AnnotationText>Annotation</AnnotationText>"
1637 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1638 "<ReelNumber>1</ReelNumber>"
1639 "<Language>de-DE</Language>"
1640 "<EditRate>24 1</EditRate>"
1641 "<TimeCodeRate>24</TimeCodeRate>"
1642 "<StartTime>00:00:02:00</StartTime>"
1643 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1645 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1646 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1647 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1653 dcp::File xml_file(dir / "subs.xml", "w");
1654 BOOST_REQUIRE (xml_file);
1655 xml_file.write(xml.c_str(), xml.size(), 1);
1657 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1658 subs->write (dir / "subs.mxf");
1660 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1661 dcp->cpls().front()->reels().front()->add(reel_subs);
1664 check_verify_result (
1668 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1669 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1677 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1680 , v_position(v_position_)
1688 dcp::VAlign v_align;
1694 shared_ptr<dcp::CPL>
1695 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
1697 prepare_directory (dir);
1698 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1699 asset->set_start_time (dcp::Time());
1700 for (auto i: subs) {
1701 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1703 asset->set_language (dcp::LanguageTag("de-DE"));
1704 if (key && key_id) {
1705 asset->set_key(*key);
1706 asset->set_key_id(*key_id);
1709 asset->write (dir / "subs.mxf");
1711 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1712 return write_dcp_with_single_asset (dir, reel_asset);
1717 shared_ptr<dcp::CPL>
1718 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1720 prepare_directory (dir);
1721 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1722 asset->set_start_time (dcp::Time());
1723 asset->set_language (dcp::LanguageTag("de-DE"));
1725 auto subs_mxf = dir / "subs.mxf";
1726 asset->write (subs_mxf);
1728 /* The call to write() puts the asset into the DCP correctly but it will have
1729 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1732 ASDCP::TimedText::MXFWriter writer;
1733 ASDCP::WriterInfo writer_info;
1734 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1736 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1737 DCP_ASSERT (c == Kumu::UUID_Length);
1738 ASDCP::TimedText::TimedTextDescriptor descriptor;
1739 descriptor.ContainerDuration = asset->intrinsic_duration();
1740 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1741 DCP_ASSERT (c == Kumu::UUID_Length);
1742 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1743 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1744 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1745 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1748 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1749 return write_dcp_with_single_asset (dir, reel_asset);
1753 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1755 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1756 /* Just too early */
1757 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1758 check_verify_result (
1762 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1763 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1769 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1771 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1772 /* Just late enough */
1773 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1774 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1778 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1780 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1781 prepare_directory (dir);
1783 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1784 asset1->set_start_time (dcp::Time());
1785 /* Just late enough */
1786 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1787 asset1->set_language (dcp::LanguageTag("de-DE"));
1789 asset1->write (dir / "subs1.mxf");
1790 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1791 auto reel1 = make_shared<dcp::Reel>();
1792 reel1->add (reel_asset1);
1793 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1794 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1795 reel1->add (markers1);
1797 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1798 asset2->set_start_time (dcp::Time());
1800 /* This would be too early on first reel but should be OK on the second */
1801 add_test_subtitle (asset2, 3, 4 * 24);
1802 asset2->set_language (dcp::LanguageTag("de-DE"));
1803 asset2->write (dir / "subs2.mxf");
1804 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1805 auto reel2 = make_shared<dcp::Reel>();
1806 reel2->add (reel_asset2);
1807 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1808 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1809 reel2->add (markers2);
1811 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1814 auto dcp = make_shared<dcp::DCP>(dir);
1816 dcp->set_annotation_text("hello");
1819 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1823 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1825 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1826 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1830 { 5 * 24 + 1, 6 * 24 },
1832 check_verify_result (
1836 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1837 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1842 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1844 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1845 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1849 { 5 * 24 + 16, 8 * 24 },
1851 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1855 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1857 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1858 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1859 check_verify_result (
1863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1869 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1871 auto const dir = path("build/test/verify_valid_subtitle_duration");
1872 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1873 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1877 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1879 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1880 prepare_directory (dir);
1881 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1882 asset->set_start_time (dcp::Time());
1883 add_test_subtitle (asset, 0, 4 * 24);
1885 asset->set_language (dcp::LanguageTag("de-DE"));
1886 asset->write (dir / "subs.mxf");
1888 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1889 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1890 check_verify_result (
1894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1895 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1896 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1903 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1905 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1906 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1909 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1910 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1911 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1912 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1914 check_verify_result (
1918 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1924 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1926 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1927 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1930 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1931 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1932 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1934 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1938 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1940 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1941 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1944 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1945 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1946 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1947 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1949 check_verify_result (
1953 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1954 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1959 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1961 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1962 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1965 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1966 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1967 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1968 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1970 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1974 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1976 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1977 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1980 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1982 check_verify_result (
1986 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1992 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1994 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1995 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1998 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2000 check_verify_result (
2004 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2005 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2010 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2012 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2013 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2016 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2017 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2018 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2019 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2021 check_verify_result (
2025 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2026 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2031 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2033 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2034 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2037 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2038 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2039 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2041 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2045 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2047 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2048 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2051 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2052 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2053 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2054 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2056 check_verify_result (
2060 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2066 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2068 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2069 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2072 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2073 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2074 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2075 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2077 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2081 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2083 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2084 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2087 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2089 check_verify_result (
2093 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2098 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2100 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2101 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2104 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2106 check_verify_result (
2110 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2116 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2118 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2119 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2122 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2123 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2124 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2126 check_verify_result (
2130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2135 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2137 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2138 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2141 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2142 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2143 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2145 check_verify_result (
2149 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2150 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2155 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2157 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2158 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2161 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2162 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2163 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2165 check_verify_result (
2169 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2174 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2176 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2177 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2180 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2181 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2182 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2184 check_verify_result (
2188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2193 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2195 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2196 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2197 check_verify_result (
2201 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2202 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2207 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2209 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2210 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2211 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2221 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2223 path const dir("build/test/verify_invalid_sound_frame_rate");
2224 prepare_directory (dir);
2226 auto picture = simple_picture (dir, "foo");
2227 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2228 auto reel = make_shared<dcp::Reel>();
2229 reel->add (reel_picture);
2230 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2231 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2232 reel->add (reel_sound);
2233 reel->add (simple_markers());
2234 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2236 auto dcp = make_shared<dcp::DCP>(dir);
2238 dcp->set_annotation_text("hello");
2241 check_verify_result (
2245 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2246 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2251 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2253 path const dir("build/test/verify_missing_cpl_annotation_text");
2254 auto dcp = make_simple (dir);
2257 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2259 auto const cpl = dcp->cpls()[0];
2262 BOOST_REQUIRE (cpl->file());
2263 Editor e(cpl->file().get());
2264 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2267 check_verify_result (
2271 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2272 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2277 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2279 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2280 auto dcp = make_simple (dir);
2283 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2284 auto const cpl = dcp->cpls()[0];
2287 BOOST_REQUIRE (cpl->file());
2288 Editor e(cpl->file().get());
2289 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2292 check_verify_result (
2296 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2297 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2302 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2304 path const dir("build/test/verify_mismatched_asset_duration");
2305 prepare_directory (dir);
2306 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2307 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2309 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2310 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2312 auto reel = make_shared<dcp::Reel>(
2313 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2314 make_shared<dcp::ReelSoundAsset>(ms, 0)
2317 reel->add (simple_markers());
2321 dcp->set_annotation_text("A Test DCP");
2324 check_verify_result (
2328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2329 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2336 shared_ptr<dcp::CPL>
2337 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2339 prepare_directory (dir);
2340 auto dcp = make_shared<dcp::DCP>(dir);
2341 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2343 auto constexpr reel_length = 192;
2345 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2346 subs->set_language (dcp::LanguageTag("de-DE"));
2347 subs->set_start_time (dcp::Time());
2348 subs->add (simple_subtitle());
2350 subs->write (dir / "subs.mxf");
2351 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2353 auto reel1 = make_shared<dcp::Reel>(
2354 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2355 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2359 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2362 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2363 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2364 reel1->add (markers1);
2368 auto reel2 = make_shared<dcp::Reel>(
2369 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2370 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2374 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2377 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2378 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2379 reel2->add (markers2);
2384 dcp->set_annotation_text("A Test DCP");
2391 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2394 path dir ("build/test/missing_main_subtitle_from_some_reels");
2395 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2396 check_verify_result (
2400 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2401 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2407 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2408 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2409 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2413 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2414 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2415 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2421 shared_ptr<dcp::CPL>
2422 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2424 prepare_directory (dir);
2425 auto dcp = make_shared<dcp::DCP>(dir);
2426 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2428 auto constexpr reel_length = 192;
2430 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2431 subs->set_language (dcp::LanguageTag("de-DE"));
2432 subs->set_start_time (dcp::Time());
2433 subs->add (simple_subtitle());
2435 subs->write (dir / "subs.mxf");
2437 auto reel1 = make_shared<dcp::Reel>(
2438 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2439 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2442 for (int i = 0; i < caps_in_reel1; ++i) {
2443 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2446 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2447 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2448 reel1->add (markers1);
2452 auto reel2 = make_shared<dcp::Reel>(
2453 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2454 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2457 for (int i = 0; i < caps_in_reel2; ++i) {
2458 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2461 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2462 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2463 reel2->add (markers2);
2468 dcp->set_annotation_text("A Test DCP");
2475 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2478 path dir ("build/test/mismatched_closed_caption_asset_counts");
2479 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2480 check_verify_result (
2484 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2485 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2490 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2491 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2492 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2496 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2497 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2498 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2505 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2507 prepare_directory (dir);
2508 auto dcp = make_shared<dcp::DCP>(dir);
2509 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2511 auto constexpr reel_length = 192;
2513 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2514 subs->set_language (dcp::LanguageTag("de-DE"));
2515 subs->set_start_time (dcp::Time());
2516 subs->add (simple_subtitle());
2518 subs->write (dir / "subs.mxf");
2519 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2522 auto reel = make_shared<dcp::Reel>(
2523 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2524 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2527 reel->add (reel_text);
2529 reel->add (simple_markers(reel_length));
2534 dcp->set_annotation_text("A Test DCP");
2537 check_verify_result (
2541 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2542 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2547 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2549 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2550 "build/test/verify_subtitle_entry_point_must_be_present",
2551 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2552 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2553 asset->unset_entry_point ();
2557 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2558 "build/test/verify_subtitle_entry_point_must_be_zero",
2559 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2560 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2561 asset->set_entry_point (4);
2565 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2566 "build/test/verify_closed_caption_entry_point_must_be_present",
2567 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2568 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2569 asset->unset_entry_point ();
2573 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2574 "build/test/verify_closed_caption_entry_point_must_be_zero",
2575 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2576 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2577 asset->set_entry_point (9);
2583 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2587 path const dir("build/test/verify_missing_hash");
2588 auto dcp = make_simple (dir);
2591 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2592 auto const cpl = dcp->cpls()[0];
2593 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2594 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2595 auto asset_id = cpl->reels()[0]->main_picture()->id();
2598 BOOST_REQUIRE (cpl->file());
2599 Editor e(cpl->file().get());
2600 e.delete_first_line_containing("<Hash>");
2603 check_verify_result (
2607 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2608 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2615 verify_markers_test (
2617 vector<pair<dcp::Marker, dcp::Time>> markers,
2618 vector<dcp::VerificationNote> test_notes
2621 auto dcp = make_simple (dir);
2622 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2623 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2624 for (auto const& i: markers) {
2625 markers_asset->set (i.first, i.second);
2627 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2630 check_verify_result({dir}, {}, test_notes);
2634 BOOST_AUTO_TEST_CASE (verify_markers)
2636 verify_markers_test (
2637 "build/test/verify_markers_all_correct",
2639 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2640 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2641 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2642 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2647 verify_markers_test (
2648 "build/test/verify_markers_missing_ffec",
2650 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2651 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2652 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2658 verify_markers_test (
2659 "build/test/verify_markers_missing_ffmc",
2661 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2662 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2663 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2666 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2669 verify_markers_test (
2670 "build/test/verify_markers_missing_ffoc",
2672 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2673 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2674 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2677 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2680 verify_markers_test (
2681 "build/test/verify_markers_missing_lfoc",
2683 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2684 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2685 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2691 verify_markers_test (
2692 "build/test/verify_markers_incorrect_ffoc",
2694 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2695 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2696 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2697 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2700 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2703 verify_markers_test (
2704 "build/test/verify_markers_incorrect_lfoc",
2706 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2707 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2708 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2709 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2712 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2717 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2719 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2720 prepare_directory (dir);
2721 auto dcp = make_simple (dir);
2722 auto cpl = dcp->cpls()[0];
2723 cpl->unset_version_number();
2726 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2730 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2732 path dir = "build/test/verify_missing_extension_metadata1";
2733 auto dcp = make_simple (dir);
2736 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2737 auto cpl = dcp->cpls()[0];
2740 Editor e (cpl->file().get());
2741 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2744 check_verify_result (
2748 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2749 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2754 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2756 path dir = "build/test/verify_missing_extension_metadata2";
2757 auto dcp = make_simple (dir);
2760 auto cpl = dcp->cpls()[0];
2763 Editor e (cpl->file().get());
2764 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2767 check_verify_result (
2771 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2772 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2777 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2779 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2780 auto dcp = make_simple (dir);
2783 auto const cpl = dcp->cpls()[0];
2786 Editor e (cpl->file().get());
2787 e.replace ("<meta:Name>A", "<meta:NameX>A");
2788 e.replace ("n</meta:Name>", "n</meta:NameX>");
2791 check_verify_result (
2795 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2796 { 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 },
2797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2802 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2804 path dir = "build/test/verify_invalid_extension_metadata1";
2805 auto dcp = make_simple (dir);
2808 auto cpl = dcp->cpls()[0];
2811 Editor e (cpl->file().get());
2812 e.replace ("Application", "Fred");
2815 check_verify_result (
2819 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2820 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2825 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2827 path dir = "build/test/verify_invalid_extension_metadata2";
2828 auto dcp = make_simple (dir);
2831 auto cpl = dcp->cpls()[0];
2834 Editor e (cpl->file().get());
2835 e.replace ("DCP Constraints Profile", "Fred");
2838 check_verify_result (
2842 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2843 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2848 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2850 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2851 auto dcp = make_simple (dir);
2854 auto const cpl = dcp->cpls()[0];
2857 Editor e (cpl->file().get());
2858 e.replace ("<meta:Value>", "<meta:ValueX>");
2859 e.replace ("</meta:Value>", "</meta:ValueX>");
2862 check_verify_result (
2866 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2867 { 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 },
2868 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2873 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2875 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2876 auto dcp = make_simple (dir);
2879 auto const cpl = dcp->cpls()[0];
2882 Editor e (cpl->file().get());
2883 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2886 check_verify_result (
2890 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2891 { 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() },
2896 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2898 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2899 auto dcp = make_simple (dir);
2902 auto const cpl = dcp->cpls()[0];
2905 Editor e (cpl->file().get());
2906 e.replace ("<meta:Property>", "<meta:PropertyX>");
2907 e.replace ("</meta:Property>", "</meta:PropertyX>");
2910 check_verify_result (
2914 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2915 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2916 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2921 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2923 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2924 auto dcp = make_simple (dir);
2927 auto const cpl = dcp->cpls()[0];
2930 Editor e (cpl->file().get());
2931 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2932 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2935 check_verify_result (
2939 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2940 { 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 },
2941 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2947 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2949 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2950 prepare_directory (dir);
2951 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2952 copy_file (i.path(), dir / i.path().filename());
2955 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml" );
2956 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
2960 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2963 check_verify_result (
2967 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id(), canonical(cpl) },
2968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl), },
2969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2970 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2971 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2972 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2973 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2974 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id(), canonical(cpl) }
2979 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2981 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2982 prepare_directory (dir);
2983 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2984 copy_file (i.path(), dir / i.path().filename());
2987 path const cpl = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
2988 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
2991 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2994 check_verify_result (
2998 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) },
2999 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3000 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3001 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3002 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3003 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
3004 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) },
3009 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3011 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3012 prepare_directory (dir);
3013 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3014 copy_file (i.path(), dir / i.path().filename());
3018 Editor e (dir / dcp_test1_pkl());
3019 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3022 check_verify_result({dir}, {}, {});
3026 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3028 path dir ("build/test/verify_must_not_be_partially_encrypted");
3029 prepare_directory (dir);
3033 auto signer = make_shared<dcp::CertificateChain>();
3034 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3035 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3036 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3037 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3039 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3043 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3046 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3047 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3048 for (int i = 0; i < 24; ++i) {
3049 writer->write (j2c.data(), j2c.size());
3051 writer->finalize ();
3053 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3055 auto reel = make_shared<dcp::Reel>(
3056 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3057 make_shared<dcp::ReelSoundAsset>(ms, 0)
3060 reel->add (simple_markers());
3064 cpl->set_content_version (
3065 {"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"}
3067 cpl->set_annotation_text ("A Test DCP");
3068 cpl->set_issuer ("OpenDCP 0.0.25");
3069 cpl->set_creator ("OpenDCP 0.0.25");
3070 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3071 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3072 cpl->set_main_sound_sample_rate (48000);
3073 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3074 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3075 cpl->set_version_number (1);
3079 d.set_issuer("OpenDCP 0.0.25");
3080 d.set_creator("OpenDCP 0.0.25");
3081 d.set_issue_date("2012-07-17T04:45:18+00:00");
3082 d.set_annotation_text("A Test DCP");
3083 d.write_xml(signer);
3085 check_verify_result (
3089 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3094 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3096 vector<dcp::VerificationNote> notes;
3097 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"));
3098 auto reader = picture.start_read ();
3099 auto frame = reader->get_frame (0);
3100 verify_j2k(frame, 0, 24, notes);
3101 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3105 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3107 vector<dcp::VerificationNote> notes;
3108 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3109 auto reader = picture.start_read ();
3110 auto frame = reader->get_frame (0);
3111 verify_j2k(frame, 0, 24, notes);
3112 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3116 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3118 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3119 prepare_directory (dir);
3120 auto dcp = make_simple (dir);
3122 vector<dcp::VerificationNote> notes;
3123 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3124 auto reader = picture.start_read ();
3125 auto frame = reader->get_frame (0);
3126 verify_j2k(frame, 0, 24, notes);
3127 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3131 /** Check that ResourceID and the XML ID being different is spotted */
3132 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3134 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3135 prepare_directory (dir);
3137 ASDCP::WriterInfo writer_info;
3138 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3141 auto mxf_id = dcp::make_uuid ();
3142 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3143 BOOST_REQUIRE (c == Kumu::UUID_Length);
3145 auto resource_id = dcp::make_uuid ();
3146 ASDCP::TimedText::TimedTextDescriptor descriptor;
3147 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3148 DCP_ASSERT (c == Kumu::UUID_Length);
3150 auto xml_id = dcp::make_uuid ();
3151 ASDCP::TimedText::MXFWriter writer;
3152 auto subs_mxf = dir / "subs.mxf";
3153 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3154 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3155 writer.WriteTimedTextResource (dcp::String::compose(
3156 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3157 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3158 "<Id>urn:uuid:%1</Id>"
3159 "<ContentTitleText>Content</ContentTitleText>"
3160 "<AnnotationText>Annotation</AnnotationText>"
3161 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3162 "<ReelNumber>1</ReelNumber>"
3163 "<Language>en-US</Language>"
3164 "<EditRate>25 1</EditRate>"
3165 "<TimeCodeRate>25</TimeCodeRate>"
3166 "<StartTime>00:00:00:00</StartTime>"
3167 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3169 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3170 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3171 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3180 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3181 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3183 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3185 check_verify_result (
3189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3191 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3192 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3197 /** Check that ResourceID and the MXF ID being the same is spotted */
3198 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3200 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3201 prepare_directory (dir);
3203 ASDCP::WriterInfo writer_info;
3204 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3207 auto mxf_id = dcp::make_uuid ();
3208 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3209 BOOST_REQUIRE (c == Kumu::UUID_Length);
3211 auto resource_id = mxf_id;
3212 ASDCP::TimedText::TimedTextDescriptor descriptor;
3213 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3214 DCP_ASSERT (c == Kumu::UUID_Length);
3216 auto xml_id = resource_id;
3217 ASDCP::TimedText::MXFWriter writer;
3218 auto subs_mxf = dir / "subs.mxf";
3219 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3220 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3221 writer.WriteTimedTextResource (dcp::String::compose(
3222 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3223 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3224 "<Id>urn:uuid:%1</Id>"
3225 "<ContentTitleText>Content</ContentTitleText>"
3226 "<AnnotationText>Annotation</AnnotationText>"
3227 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3228 "<ReelNumber>1</ReelNumber>"
3229 "<Language>en-US</Language>"
3230 "<EditRate>25 1</EditRate>"
3231 "<TimeCodeRate>25</TimeCodeRate>"
3232 "<StartTime>00:00:00:00</StartTime>"
3233 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3235 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3236 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3237 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3246 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3247 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3249 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3251 check_verify_result (
3255 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3256 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3257 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3258 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3259 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3264 /** Check a DCP with a 3D asset marked as 2D */
3265 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3267 check_verify_result (
3268 { private_test / "data" / "xm" },
3272 dcp::VerificationNote::Type::WARNING,
3273 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3276 dcp::VerificationNote::Type::BV21_ERROR,
3277 dcp::VerificationNote::Code::INVALID_STANDARD
3284 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3286 path dir = "build/test/verify_unexpected_things_in_main_markers";
3287 prepare_directory (dir);
3288 auto dcp = make_simple (dir, 1, 24);
3292 Editor e (find_cpl(dir));
3294 " <IntrinsicDuration>24</IntrinsicDuration>",
3295 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3299 dcp::CPL cpl (find_cpl(dir));
3301 check_verify_result (
3305 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3306 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3307 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3312 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3314 path dir = "build/test/verify_invalid_content_kind";
3315 prepare_directory (dir);
3316 auto dcp = make_simple (dir, 1, 24);
3320 Editor e(find_cpl(dir));
3321 e.replace("trailer", "trip");
3324 dcp::CPL cpl (find_cpl(dir));
3326 check_verify_result (
3330 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3331 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3337 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3339 path dir = "build/test/verify_valid_content_kind";
3340 prepare_directory (dir);
3341 auto dcp = make_simple (dir, 1, 24);
3345 Editor e(find_cpl(dir));
3346 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3349 dcp::CPL cpl (find_cpl(dir));
3351 check_verify_result (
3355 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3361 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3363 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3364 prepare_directory(dir);
3365 auto dcp = make_simple(dir, 1, 24);
3368 auto constexpr area = "<meta:MainPictureActiveArea>";
3371 Editor e(find_cpl(dir));
3372 e.delete_lines_after(area, 2);
3373 e.insert(area, "<meta:Height>4080</meta:Height>");
3374 e.insert(area, "<meta:Width>1997</meta:Width>");
3377 dcp::PKL pkl(find_pkl(dir));
3378 dcp::CPL cpl(find_cpl(dir));
3380 check_verify_result(
3384 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3385 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3386 { 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)) },
3391 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3393 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3394 prepare_directory(dir);
3395 auto dcp = make_simple(dir, 1, 24);
3398 auto constexpr area = "<meta:MainPictureActiveArea>";
3401 Editor e(find_cpl(dir));
3402 e.delete_lines_after(area, 2);
3403 e.insert(area, "<meta:Height>5125</meta:Height>");
3404 e.insert(area, "<meta:Width>9900</meta:Width>");
3407 dcp::PKL pkl(find_pkl(dir));
3408 dcp::CPL cpl(find_cpl(dir));
3410 check_verify_result(
3414 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3415 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3416 { 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)) },
3417 { 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)) },
3422 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3426 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3427 prepare_directory(dir);
3428 auto dcp = make_simple(dir, 1, 24);
3432 Editor e(find_pkl(dir));
3433 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3436 dcp::PKL pkl(find_pkl(dir));
3438 check_verify_result(
3442 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3447 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3451 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3452 prepare_directory(dir);
3453 auto dcp = make_simple(dir, 1, 24);
3457 Editor e(find_asset_map(dir));
3458 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3461 dcp::PKL pkl(find_pkl(dir));
3462 dcp::AssetMap asset_map(find_asset_map(dir));
3464 check_verify_result(
3468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3469 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3474 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3476 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3478 dcp::MXFMetadata mxf_meta;
3479 mxf_meta.company_name = "OpenDCP";
3480 mxf_meta.product_name = "OpenDCP";
3481 mxf_meta.product_version = "0.0.25";
3483 auto constexpr sample_rate = 48000;
3484 auto constexpr frames = 240;
3486 boost::filesystem::remove_all(path);
3487 boost::filesystem::create_directories(path);
3488 auto dcp = make_shared<dcp::DCP>(path);
3489 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3490 cpl->set_annotation_text("hello");
3491 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3492 cpl->set_main_sound_sample_rate(sample_rate);
3493 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3494 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3495 cpl->set_version_number(1);
3499 /* Reel with 2 channels of audio */
3501 auto mp = simple_picture(path, "1", frames, {});
3502 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3504 auto reel = make_shared<dcp::Reel>(
3505 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3506 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3509 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3510 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3517 /* Reel with 6 channels of audio */
3519 auto mp = simple_picture(path, "2", frames, {});
3520 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3522 auto reel = make_shared<dcp::Reel>(
3523 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3524 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3527 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3528 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3535 dcp->set_annotation_text("hello");
3538 check_verify_result(
3542 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3547 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3549 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3551 dcp::MXFMetadata mxf_meta;
3552 mxf_meta.company_name = "OpenDCP";
3553 mxf_meta.product_name = "OpenDCP";
3554 mxf_meta.product_version = "0.0.25";
3556 auto constexpr sample_rate = 48000;
3557 auto constexpr frames = 240;
3559 boost::filesystem::remove_all(path);
3560 boost::filesystem::create_directories(path);
3561 auto dcp = make_shared<dcp::DCP>(path);
3562 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3563 cpl->set_annotation_text("hello");
3564 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3565 cpl->set_main_sound_sample_rate(sample_rate);
3566 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3567 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3568 cpl->set_version_number(1);
3570 auto mp = simple_picture(path, "1", frames, {});
3571 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3573 auto reel = make_shared<dcp::Reel>(
3574 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3575 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3578 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3579 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3580 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3586 dcp->set_annotation_text("hello");
3589 check_verify_result(
3593 { 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)) },
3598 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3600 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3601 auto constexpr video_frames = 24;
3602 auto constexpr sample_rate = 48000;
3604 boost::filesystem::remove_all(path);
3605 boost::filesystem::create_directories(path);
3607 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3608 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3610 dcp::Size const size(1998, 1080);
3611 auto image = make_shared<dcp::OpenJPEGImage>(size);
3612 boost::random::mt19937 rng(1);
3613 boost::random::uniform_int_distribution<> dist(0, 4095);
3614 for (int c = 0; c < 3; ++c) {
3615 for (int p = 0; p < (1998 * 1080); ++p) {
3616 image->data(c)[p] = dist(rng);
3619 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3620 for (int i = 0; i < 24; ++i) {
3621 picture_writer->write(j2c.data(), j2c.size());
3623 picture_writer->finalize();
3625 auto dcp = make_shared<dcp::DCP>(path);
3626 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3627 cpl->set_content_version(
3628 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3630 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3631 cpl->set_main_sound_sample_rate(sample_rate);
3632 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3633 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3634 cpl->set_version_number(1);
3636 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3638 auto reel = make_shared<dcp::Reel>(
3639 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3640 make_shared<dcp::ReelSoundAsset>(ms, 0)
3645 dcp->set_annotation_text("A Test DCP");
3648 vector<dcp::VerificationNote> expected;
3651 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") }
3654 int component_sizes[] = {
3660 for (auto frame = 0; frame < 24; frame++) {
3661 for (auto component = 0; component < 3; component++) {
3663 dcp::VerificationNote(
3664 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
3665 ).set_frame(frame).set_component(component).set_size(component_sizes[component])
3671 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC }
3675 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3678 check_verify_result({ path }, {}, expected);
3682 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3684 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3685 check_verify_result(
3689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3690 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3691 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3699 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3701 path const dir("build/test/verify_missing_load_font");
3702 prepare_directory (dir);
3703 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3705 Editor editor(dir / "subs.xml");
3706 editor.delete_first_line_containing("LoadFont");
3708 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3709 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3710 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3712 check_verify_result (
3716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3717 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3723 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3725 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3726 prepare_directory(dir);
3727 auto dcp = make_simple (dir, 1, 202);
3730 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3731 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3732 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3733 "<ContentTitleText>Content</ContentTitleText>"
3734 "<AnnotationText>Annotation</AnnotationText>"
3735 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3736 "<ReelNumber>1</ReelNumber>"
3737 "<EditRate>24 1</EditRate>"
3738 "<TimeCodeRate>24</TimeCodeRate>"
3739 "<StartTime>00:00:00:00</StartTime>"
3740 "<Language>de-DE</Language>"
3742 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3743 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3744 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3750 dcp::File xml_file(dir / "subs.xml", "w");
3751 BOOST_REQUIRE(xml_file);
3752 xml_file.write(xml.c_str(), xml.size(), 1);
3754 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3755 subs->write(dir / "subs.mxf");
3757 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3758 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3761 check_verify_result (
3765 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3770 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3772 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3773 boost::filesystem::remove_all(dir);
3775 auto dcp1 = make_simple(dir / "1");
3778 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3780 auto dcp2 = make_simple(dir / "2");
3782 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3784 boost::filesystem::remove(dir / "1" / "video.mxf");
3785 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3787 check_verify_result(
3791 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3796 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3798 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3799 boost::filesystem::remove_all(dir);
3801 auto dcp = make_simple(dir);
3802 BOOST_REQUIRE(dcp->cpls().size() == 1);
3803 auto cpl = dcp->cpls()[0];
3804 cpl->set_content_version(dcp::ContentVersion(""));
3807 check_verify_result(
3811 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())
3816 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
3817 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
3819 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
3821 auto key_id = dcp::make_uuid();
3822 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
3824 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
3825 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
3827 path const pkl_file = find_file(dir, "pkl_");
3828 path const cpl_file = find_file(dir, "cpl_");
3830 check_verify_result(
3834 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl_file) },
3835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), canonical(cpl_file) },
3836 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file) }