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 vector<dcp::VerificationNote> expected;
639 for (auto i = 0; i < 24; ++i) {
641 dcp::VerificationNote(
642 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
643 ).set_frame(i).set_frame_rate(24)
648 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") }
652 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
655 check_verify_result({ dir }, {}, expected);
659 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
661 int const nearly_too_big = 1302083 * 0.98;
663 /* Compress a black image */
664 auto image = black_image ();
665 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
666 BOOST_REQUIRE (frame.size() < nearly_too_big);
668 /* Place it in a bigger block with some zero padding at the end */
669 dcp::ArrayData oversized_frame(nearly_too_big);
670 memcpy (oversized_frame.data(), frame.data(), frame.size());
671 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
673 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
674 prepare_directory (dir);
675 auto cpl = dcp_from_frame (oversized_frame, dir);
677 vector<dcp::VerificationNote> expected;
679 for (auto i = 0; i < 24; ++i) {
681 dcp::VerificationNote(
682 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")
683 ).set_frame(i).set_frame_rate(24)
688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") }
692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
695 check_verify_result ({ dir }, {}, expected);
699 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
701 /* Compress a black image */
702 auto image = black_image ();
703 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
704 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
706 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
707 prepare_directory (dir);
708 auto cpl = dcp_from_frame (frame, dir);
710 check_verify_result({ dir }, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
714 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
716 path const dir("build/test/verify_valid_interop_subtitles");
717 prepare_directory (dir);
718 copy_file ("test/data/subs1.xml", dir / "subs.xml");
719 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
720 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
721 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
723 check_verify_result (
727 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
733 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
735 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
736 prepare_directory(dir);
737 copy_file("test/data/subs1.xml", dir / "ccap.xml");
738 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
739 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
740 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
742 check_verify_result (
746 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
747 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
752 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
754 using namespace boost::filesystem;
756 path const dir("build/test/verify_invalid_interop_subtitles");
757 prepare_directory (dir);
758 copy_file ("test/data/subs1.xml", dir / "subs.xml");
759 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
760 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
761 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
764 Editor e (dir / "subs.xml");
765 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
768 check_verify_result (
772 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
773 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
775 dcp::VerificationNote::Type::ERROR,
776 dcp::VerificationNote::Code::INVALID_XML,
777 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
781 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
786 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
788 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
789 prepare_directory(dir);
790 copy_file("test/data/subs4.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_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
801 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
807 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
809 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
810 prepare_directory(dir);
811 copy_file("test/data/subs5.xml", dir / "subs.xml");
812 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
813 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
814 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
816 check_verify_result (
820 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
821 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
827 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
829 path const dir("build/test/verify_valid_smpte_subtitles");
830 prepare_directory (dir);
831 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
832 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
833 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
834 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
840 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
841 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
842 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
847 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
849 using namespace boost::filesystem;
851 path const dir("build/test/verify_invalid_smpte_subtitles");
852 prepare_directory (dir);
853 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
854 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
855 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
856 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
857 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
859 check_verify_result (
863 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
865 dcp::VerificationNote::Type::ERROR,
866 dcp::VerificationNote::Code::INVALID_XML,
867 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
871 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
872 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
873 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
874 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
879 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
881 path const dir("build/test/verify_empty_text_node_in_subtitles");
882 prepare_directory (dir);
883 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
884 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
885 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
886 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
888 check_verify_result (
892 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
893 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
896 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
897 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
902 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
903 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
905 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
906 prepare_directory (dir);
907 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
908 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
909 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
910 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
912 check_verify_result (
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
917 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
922 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
923 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
925 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
926 prepare_directory (dir);
927 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
928 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
929 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
930 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
932 check_verify_result (
936 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
938 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
939 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
944 BOOST_AUTO_TEST_CASE (verify_external_asset)
946 path const ov_dir("build/test/verify_external_asset");
947 prepare_directory (ov_dir);
949 auto image = black_image ();
950 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
951 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
952 dcp_from_frame (frame, ov_dir);
954 dcp::DCP ov (ov_dir);
957 path const vf_dir("build/test/verify_external_asset_vf");
958 prepare_directory (vf_dir);
960 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
961 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
963 check_verify_result (
967 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
973 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
975 path const dir("build/test/verify_valid_cpl_metadata");
976 prepare_directory (dir);
978 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
979 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
980 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
982 auto reel = make_shared<dcp::Reel>();
983 reel->add (reel_asset);
985 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
986 reel->add (simple_markers(16 * 24));
988 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
990 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
991 cpl->set_main_sound_sample_rate (48000);
992 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
993 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
994 cpl->set_version_number (1);
998 dcp.set_annotation_text("hello");
1004 find_prefix(path dir, string prefix)
1006 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1007 return boost::starts_with(p.filename().string(), prefix);
1010 BOOST_REQUIRE(iter != directory_iterator());
1011 return iter->path();
1015 path find_cpl (path dir)
1017 return find_prefix(dir, "cpl_");
1024 return find_prefix(dir, "pkl_");
1029 find_asset_map(path dir)
1031 return find_prefix(dir, "ASSETMAP");
1035 /* DCP with invalid CompositionMetadataAsset */
1036 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1038 using namespace boost::filesystem;
1040 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1041 prepare_directory (dir);
1043 auto reel = make_shared<dcp::Reel>();
1044 reel->add (black_picture_asset(dir));
1045 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1047 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1048 cpl->set_main_sound_sample_rate (48000);
1049 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1050 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1051 cpl->set_version_number (1);
1053 reel->add (simple_markers());
1057 dcp.set_annotation_text("hello");
1061 Editor e (find_cpl(dir));
1062 e.replace ("MainSound", "MainSoundX");
1065 check_verify_result (
1069 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1070 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1072 dcp::VerificationNote::Type::ERROR,
1073 dcp::VerificationNote::Code::INVALID_XML,
1074 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1075 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1076 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1077 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1078 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1079 "ExtensionMetadataList?,)'"),
1080 canonical(cpl->file().get()),
1083 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1088 /* DCP with invalid CompositionMetadataAsset */
1089 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1091 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1092 prepare_directory (dir);
1094 auto reel = make_shared<dcp::Reel>();
1095 reel->add (black_picture_asset(dir));
1096 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1098 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1099 cpl->set_main_sound_sample_rate (48000);
1100 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1101 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1105 dcp.set_annotation_text("hello");
1109 Editor e (find_cpl(dir));
1110 e.replace ("meta:Width", "meta:WidthX");
1113 check_verify_result (
1116 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1121 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1123 path const dir("build/test/verify_invalid_language1");
1124 prepare_directory (dir);
1125 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1126 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1127 asset->_language = "wrong-andbad";
1128 asset->write (dir / "subs.mxf");
1129 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1130 reel_asset->_language = "badlang";
1131 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1133 check_verify_result (
1137 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1139 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1144 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1145 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1147 path const dir("build/test/verify_invalid_language2");
1148 prepare_directory (dir);
1149 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1150 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1151 asset->_language = "wrong-andbad";
1152 asset->write (dir / "subs.mxf");
1153 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1154 reel_asset->_language = "badlang";
1155 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1157 check_verify_result (
1161 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1162 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1163 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1168 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1169 * the release territory.
1171 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1173 path const dir("build/test/verify_invalid_language3");
1174 prepare_directory (dir);
1176 auto picture = simple_picture (dir, "foo");
1177 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1178 auto reel = make_shared<dcp::Reel>();
1179 reel->add (reel_picture);
1180 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1181 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1182 reel->add (reel_sound);
1183 reel->add (simple_markers());
1185 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1187 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1188 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1189 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1190 cpl->set_main_sound_sample_rate (48000);
1191 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1192 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1193 cpl->set_version_number (1);
1194 cpl->_release_territory = "fred-jim";
1195 auto dcp = make_shared<dcp::DCP>(dir);
1197 dcp->set_annotation_text("hello");
1200 check_verify_result (
1204 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1205 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1206 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1207 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1213 vector<dcp::VerificationNote>
1214 check_picture_size (int width, int height, int frame_rate, bool three_d)
1216 using namespace boost::filesystem;
1218 path dcp_path = "build/test/verify_picture_test";
1219 prepare_directory (dcp_path);
1221 shared_ptr<dcp::PictureAsset> mp;
1223 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1225 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1227 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1229 auto image = black_image (dcp::Size(width, height));
1230 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1231 int const length = three_d ? frame_rate * 2 : frame_rate;
1232 for (int i = 0; i < length; ++i) {
1233 picture_writer->write (j2c.data(), j2c.size());
1235 picture_writer->finalize ();
1237 auto d = make_shared<dcp::DCP>(dcp_path);
1238 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1239 cpl->set_annotation_text ("A Test DCP");
1240 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1241 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1242 cpl->set_main_sound_sample_rate (48000);
1243 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1244 cpl->set_main_picture_active_area(dcp::Size(width, height));
1245 cpl->set_version_number (1);
1247 auto reel = make_shared<dcp::Reel>();
1250 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1252 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1255 reel->add (simple_markers(frame_rate));
1260 d->set_annotation_text("A Test DCP");
1263 return dcp::verify({dcp_path}, {}, &stage, &progress, {}, xsd_test);
1269 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1271 auto notes = check_picture_size(width, height, frame_rate, three_d);
1272 BOOST_CHECK_EQUAL (notes.size(), 0U);
1278 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1280 auto notes = check_picture_size(width, height, frame_rate, three_d);
1281 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1282 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1283 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1289 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1291 auto notes = check_picture_size(width, height, frame_rate, three_d);
1292 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1293 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1294 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1300 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1302 auto notes = check_picture_size(width, height, frame_rate, three_d);
1303 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1304 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1305 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1309 BOOST_AUTO_TEST_CASE (verify_picture_size)
1311 using namespace boost::filesystem;
1314 check_picture_size_ok (2048, 858, 24, false);
1315 check_picture_size_ok (2048, 858, 25, false);
1316 check_picture_size_ok (2048, 858, 48, false);
1317 check_picture_size_ok (2048, 858, 24, true);
1318 check_picture_size_ok (2048, 858, 25, true);
1319 check_picture_size_ok (2048, 858, 48, true);
1322 check_picture_size_ok (1998, 1080, 24, false);
1323 check_picture_size_ok (1998, 1080, 25, false);
1324 check_picture_size_ok (1998, 1080, 48, false);
1325 check_picture_size_ok (1998, 1080, 24, true);
1326 check_picture_size_ok (1998, 1080, 25, true);
1327 check_picture_size_ok (1998, 1080, 48, true);
1330 check_picture_size_ok (4096, 1716, 24, false);
1333 check_picture_size_ok (3996, 2160, 24, false);
1335 /* Bad frame size */
1336 check_picture_size_bad_frame_size (2050, 858, 24, false);
1337 check_picture_size_bad_frame_size (2048, 658, 25, false);
1338 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1339 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1341 /* Bad 2K frame rate */
1342 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1343 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1344 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1346 /* Bad 4K frame rate */
1347 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1348 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1351 auto notes = check_picture_size(3996, 2160, 24, true);
1352 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1353 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1354 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1360 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")
1363 std::make_shared<dcp::SubtitleString>(
1371 dcp::Time(start_frame, 24, 24),
1372 dcp::Time(end_frame, 24, 24),
1374 dcp::HAlign::CENTER,
1378 dcp::Direction::LTR,
1385 std::vector<dcp::Ruby>()
1391 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1393 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1394 prepare_directory (dir);
1396 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1397 for (int i = 0; i < 2048; ++i) {
1398 add_test_subtitle (asset, i * 24, i * 24 + 20);
1401 asset->set_language (dcp::LanguageTag("de-DE"));
1402 asset->write (dir / "subs.mxf");
1403 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1404 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1406 check_verify_result (
1410 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1412 dcp::VerificationNote::Type::BV21_ERROR,
1413 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1415 canonical(dir / "subs.mxf")
1417 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1424 shared_ptr<dcp::SMPTESubtitleAsset>
1425 make_large_subtitle_asset (path font_file)
1427 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1428 dcp::ArrayData big_fake_font(1024 * 1024);
1429 big_fake_font.write (font_file);
1430 for (int i = 0; i < 116; ++i) {
1431 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1439 verify_timed_text_asset_too_large (string name)
1441 auto const dir = path("build/test") / name;
1442 prepare_directory (dir);
1443 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1444 add_test_subtitle (asset, 0, 240);
1445 asset->set_language (dcp::LanguageTag("de-DE"));
1446 asset->write (dir / "subs.mxf");
1448 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1449 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1451 check_verify_result (
1455 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1456 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1457 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1458 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1459 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1464 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1466 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1467 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1471 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1473 path dir = "build/test/verify_missing_subtitle_language";
1474 prepare_directory (dir);
1475 auto dcp = make_simple (dir, 1, 106);
1478 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1479 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1480 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1481 "<ContentTitleText>Content</ContentTitleText>"
1482 "<AnnotationText>Annotation</AnnotationText>"
1483 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1484 "<ReelNumber>1</ReelNumber>"
1485 "<EditRate>24 1</EditRate>"
1486 "<TimeCodeRate>24</TimeCodeRate>"
1487 "<StartTime>00:00:00:00</StartTime>"
1488 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1490 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1491 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1492 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1498 dcp::File xml_file(dir / "subs.xml", "w");
1499 BOOST_REQUIRE (xml_file);
1500 xml_file.write(xml.c_str(), xml.size(), 1);
1502 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1503 subs->write (dir / "subs.mxf");
1505 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1506 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1509 check_verify_result (
1513 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1514 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1519 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1521 path path ("build/test/verify_mismatched_subtitle_languages");
1522 auto constexpr reel_length = 192;
1523 auto dcp = make_simple (path, 2, reel_length);
1524 auto cpl = dcp->cpls()[0];
1527 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1528 subs->set_language (dcp::LanguageTag("de-DE"));
1529 subs->add (simple_subtitle());
1531 subs->write (path / "subs1.mxf");
1532 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1533 cpl->reels()[0]->add(reel_subs);
1537 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1538 subs->set_language (dcp::LanguageTag("en-US"));
1539 subs->add (simple_subtitle());
1541 subs->write (path / "subs2.mxf");
1542 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1543 cpl->reels()[1]->add(reel_subs);
1548 check_verify_result (
1552 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1553 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1554 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1559 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1561 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1562 auto constexpr reel_length = 192;
1563 auto dcp = make_simple (path, 2, reel_length);
1564 auto cpl = dcp->cpls()[0];
1567 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1568 ccaps->set_language (dcp::LanguageTag("de-DE"));
1569 ccaps->add (simple_subtitle());
1571 ccaps->write (path / "subs1.mxf");
1572 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1573 cpl->reels()[0]->add(reel_ccaps);
1577 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1578 ccaps->set_language (dcp::LanguageTag("en-US"));
1579 ccaps->add (simple_subtitle());
1581 ccaps->write (path / "subs2.mxf");
1582 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1583 cpl->reels()[1]->add(reel_ccaps);
1588 check_verify_result (
1592 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1593 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1598 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1600 path dir = "build/test/verify_missing_subtitle_start_time";
1601 prepare_directory (dir);
1602 auto dcp = make_simple (dir, 1, 106);
1605 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1606 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1607 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1608 "<ContentTitleText>Content</ContentTitleText>"
1609 "<AnnotationText>Annotation</AnnotationText>"
1610 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1611 "<ReelNumber>1</ReelNumber>"
1612 "<Language>de-DE</Language>"
1613 "<EditRate>24 1</EditRate>"
1614 "<TimeCodeRate>24</TimeCodeRate>"
1615 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1617 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1618 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1619 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1625 dcp::File xml_file(dir / "subs.xml", "w");
1626 BOOST_REQUIRE (xml_file);
1627 xml_file.write(xml.c_str(), xml.size(), 1);
1629 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1630 subs->write (dir / "subs.mxf");
1632 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1633 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1636 check_verify_result (
1640 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1641 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1646 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1648 path dir = "build/test/verify_invalid_subtitle_start_time";
1649 prepare_directory (dir);
1650 auto dcp = make_simple (dir, 1, 106);
1653 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1654 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1655 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1656 "<ContentTitleText>Content</ContentTitleText>"
1657 "<AnnotationText>Annotation</AnnotationText>"
1658 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1659 "<ReelNumber>1</ReelNumber>"
1660 "<Language>de-DE</Language>"
1661 "<EditRate>24 1</EditRate>"
1662 "<TimeCodeRate>24</TimeCodeRate>"
1663 "<StartTime>00:00:02:00</StartTime>"
1664 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1666 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1667 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1668 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1674 dcp::File xml_file(dir / "subs.xml", "w");
1675 BOOST_REQUIRE (xml_file);
1676 xml_file.write(xml.c_str(), xml.size(), 1);
1678 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1679 subs->write (dir / "subs.mxf");
1681 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1682 dcp->cpls().front()->reels().front()->add(reel_subs);
1685 check_verify_result (
1689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1690 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1698 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1701 , v_position(v_position_)
1709 dcp::VAlign v_align;
1715 shared_ptr<dcp::CPL>
1716 dcp_with_text(path dir, vector<TestText> subs, optional<dcp::Key> key = boost::none, optional<string> key_id = boost::none)
1718 prepare_directory (dir);
1719 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1720 asset->set_start_time (dcp::Time());
1721 for (auto i: subs) {
1722 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1724 asset->set_language (dcp::LanguageTag("de-DE"));
1725 if (key && key_id) {
1726 asset->set_key(*key);
1727 asset->set_key_id(*key_id);
1730 asset->write (dir / "subs.mxf");
1732 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1733 return write_dcp_with_single_asset (dir, reel_asset);
1738 shared_ptr<dcp::CPL>
1739 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1741 prepare_directory (dir);
1742 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1743 asset->set_start_time (dcp::Time());
1744 asset->set_language (dcp::LanguageTag("de-DE"));
1746 auto subs_mxf = dir / "subs.mxf";
1747 asset->write (subs_mxf);
1749 /* The call to write() puts the asset into the DCP correctly but it will have
1750 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1753 ASDCP::TimedText::MXFWriter writer;
1754 ASDCP::WriterInfo writer_info;
1755 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1757 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1758 DCP_ASSERT (c == Kumu::UUID_Length);
1759 ASDCP::TimedText::TimedTextDescriptor descriptor;
1760 descriptor.ContainerDuration = asset->intrinsic_duration();
1761 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1762 DCP_ASSERT (c == Kumu::UUID_Length);
1763 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1764 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1765 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1766 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1769 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1770 return write_dcp_with_single_asset (dir, reel_asset);
1774 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1776 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1777 /* Just too early */
1778 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1779 check_verify_result (
1783 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1784 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1790 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1792 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1793 /* Just late enough */
1794 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1795 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1799 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1801 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1802 prepare_directory (dir);
1804 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1805 asset1->set_start_time (dcp::Time());
1806 /* Just late enough */
1807 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1808 asset1->set_language (dcp::LanguageTag("de-DE"));
1810 asset1->write (dir / "subs1.mxf");
1811 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1812 auto reel1 = make_shared<dcp::Reel>();
1813 reel1->add (reel_asset1);
1814 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1815 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1816 reel1->add (markers1);
1818 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1819 asset2->set_start_time (dcp::Time());
1821 /* This would be too early on first reel but should be OK on the second */
1822 add_test_subtitle (asset2, 3, 4 * 24);
1823 asset2->set_language (dcp::LanguageTag("de-DE"));
1824 asset2->write (dir / "subs2.mxf");
1825 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1826 auto reel2 = make_shared<dcp::Reel>();
1827 reel2->add (reel_asset2);
1828 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1829 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1830 reel2->add (markers2);
1832 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1835 auto dcp = make_shared<dcp::DCP>(dir);
1837 dcp->set_annotation_text("hello");
1840 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1844 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1846 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1847 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1851 { 5 * 24 + 1, 6 * 24 },
1853 check_verify_result (
1857 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1863 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1865 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1866 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1870 { 5 * 24 + 16, 8 * 24 },
1872 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1876 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1878 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1879 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1880 check_verify_result (
1884 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1890 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1892 auto const dir = path("build/test/verify_valid_subtitle_duration");
1893 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1894 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1898 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1900 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1901 prepare_directory (dir);
1902 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1903 asset->set_start_time (dcp::Time());
1904 add_test_subtitle (asset, 0, 4 * 24);
1906 asset->set_language (dcp::LanguageTag("de-DE"));
1907 asset->write (dir / "subs.mxf");
1909 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1910 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1911 check_verify_result (
1915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1916 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1917 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1924 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1926 auto const dir = path ("build/test/invalid_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" },
1933 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1935 check_verify_result (
1939 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1940 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1945 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1947 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1948 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1951 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1952 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1953 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1955 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1959 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1961 auto const dir = path ("build/test/verify_invalid_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 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1970 check_verify_result (
1974 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1975 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1980 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1982 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1983 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1986 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1987 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1988 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1989 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1991 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1995 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1997 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1998 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2001 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2003 check_verify_result (
2007 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
2008 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2013 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2015 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2016 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2019 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2021 check_verify_result (
2025 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
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_count1)
2033 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
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" },
2040 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2042 check_verify_result (
2046 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2047 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2052 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2054 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2055 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2058 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2059 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2060 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2062 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2066 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2068 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
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 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2077 check_verify_result (
2081 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2082 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2087 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2089 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2090 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2093 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2094 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2095 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2096 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2098 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2102 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2104 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2105 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2108 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2110 check_verify_result (
2114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2119 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2121 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2122 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2125 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2127 check_verify_result (
2131 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2137 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2139 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2140 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2143 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2144 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2145 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2147 check_verify_result (
2151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2156 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2158 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2159 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2162 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2163 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2164 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2166 check_verify_result (
2170 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2171 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2176 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2178 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2179 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2182 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2183 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2184 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2186 check_verify_result (
2190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2195 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2197 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2198 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2201 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2202 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2203 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2205 check_verify_result (
2209 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2214 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2216 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2217 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2218 check_verify_result (
2222 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2223 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2228 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2230 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2231 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2232 check_verify_result (
2236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2242 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2244 path const dir("build/test/verify_invalid_sound_frame_rate");
2245 prepare_directory (dir);
2247 auto picture = simple_picture (dir, "foo");
2248 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2249 auto reel = make_shared<dcp::Reel>();
2250 reel->add (reel_picture);
2251 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2252 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2253 reel->add (reel_sound);
2254 reel->add (simple_markers());
2255 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2257 auto dcp = make_shared<dcp::DCP>(dir);
2259 dcp->set_annotation_text("hello");
2262 check_verify_result (
2266 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2267 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2272 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2274 path const dir("build/test/verify_missing_cpl_annotation_text");
2275 auto dcp = make_simple (dir);
2278 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2280 auto const cpl = dcp->cpls()[0];
2283 BOOST_REQUIRE (cpl->file());
2284 Editor e(cpl->file().get());
2285 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2288 check_verify_result (
2292 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2293 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2298 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2300 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2301 auto dcp = make_simple (dir);
2304 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2305 auto const cpl = dcp->cpls()[0];
2308 BOOST_REQUIRE (cpl->file());
2309 Editor e(cpl->file().get());
2310 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2313 check_verify_result (
2317 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2318 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2323 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2325 path const dir("build/test/verify_mismatched_asset_duration");
2326 prepare_directory (dir);
2327 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2328 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2330 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2331 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2333 auto reel = make_shared<dcp::Reel>(
2334 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2335 make_shared<dcp::ReelSoundAsset>(ms, 0)
2338 reel->add (simple_markers());
2342 dcp->set_annotation_text("A Test DCP");
2345 check_verify_result (
2349 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2350 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2357 shared_ptr<dcp::CPL>
2358 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2360 prepare_directory (dir);
2361 auto dcp = make_shared<dcp::DCP>(dir);
2362 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2364 auto constexpr reel_length = 192;
2366 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2367 subs->set_language (dcp::LanguageTag("de-DE"));
2368 subs->set_start_time (dcp::Time());
2369 subs->add (simple_subtitle());
2371 subs->write (dir / "subs.mxf");
2372 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2374 auto reel1 = make_shared<dcp::Reel>(
2375 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2376 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2380 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2383 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2384 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2385 reel1->add (markers1);
2389 auto reel2 = make_shared<dcp::Reel>(
2390 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2391 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2395 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2398 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2399 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2400 reel2->add (markers2);
2405 dcp->set_annotation_text("A Test DCP");
2412 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2415 path dir ("build/test/missing_main_subtitle_from_some_reels");
2416 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2417 check_verify_result (
2421 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2422 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2428 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2429 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2430 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2434 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2435 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2436 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2442 shared_ptr<dcp::CPL>
2443 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2445 prepare_directory (dir);
2446 auto dcp = make_shared<dcp::DCP>(dir);
2447 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2449 auto constexpr reel_length = 192;
2451 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2452 subs->set_language (dcp::LanguageTag("de-DE"));
2453 subs->set_start_time (dcp::Time());
2454 subs->add (simple_subtitle());
2456 subs->write (dir / "subs.mxf");
2458 auto reel1 = make_shared<dcp::Reel>(
2459 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2460 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2463 for (int i = 0; i < caps_in_reel1; ++i) {
2464 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2467 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2468 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2469 reel1->add (markers1);
2473 auto reel2 = make_shared<dcp::Reel>(
2474 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2475 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2478 for (int i = 0; i < caps_in_reel2; ++i) {
2479 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2482 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2483 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2484 reel2->add (markers2);
2489 dcp->set_annotation_text("A Test DCP");
2496 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2499 path dir ("build/test/mismatched_closed_caption_asset_counts");
2500 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2501 check_verify_result (
2505 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2506 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2511 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2512 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2513 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2517 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2518 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2519 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2526 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2528 prepare_directory (dir);
2529 auto dcp = make_shared<dcp::DCP>(dir);
2530 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2532 auto constexpr reel_length = 192;
2534 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2535 subs->set_language (dcp::LanguageTag("de-DE"));
2536 subs->set_start_time (dcp::Time());
2537 subs->add (simple_subtitle());
2539 subs->write (dir / "subs.mxf");
2540 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2543 auto reel = make_shared<dcp::Reel>(
2544 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2545 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2548 reel->add (reel_text);
2550 reel->add (simple_markers(reel_length));
2555 dcp->set_annotation_text("A Test DCP");
2558 check_verify_result (
2562 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2563 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2568 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2570 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2571 "build/test/verify_subtitle_entry_point_must_be_present",
2572 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2573 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2574 asset->unset_entry_point ();
2578 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2579 "build/test/verify_subtitle_entry_point_must_be_zero",
2580 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2581 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2582 asset->set_entry_point (4);
2586 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2587 "build/test/verify_closed_caption_entry_point_must_be_present",
2588 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2589 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2590 asset->unset_entry_point ();
2594 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2595 "build/test/verify_closed_caption_entry_point_must_be_zero",
2596 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2597 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2598 asset->set_entry_point (9);
2604 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2608 path const dir("build/test/verify_missing_hash");
2609 auto dcp = make_simple (dir);
2612 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2613 auto const cpl = dcp->cpls()[0];
2614 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2615 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2616 auto asset_id = cpl->reels()[0]->main_picture()->id();
2619 BOOST_REQUIRE (cpl->file());
2620 Editor e(cpl->file().get());
2621 e.delete_first_line_containing("<Hash>");
2624 check_verify_result (
2628 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2629 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2636 verify_markers_test (
2638 vector<pair<dcp::Marker, dcp::Time>> markers,
2639 vector<dcp::VerificationNote> test_notes
2642 auto dcp = make_simple (dir);
2643 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2644 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2645 for (auto const& i: markers) {
2646 markers_asset->set (i.first, i.second);
2648 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2651 check_verify_result({dir}, {}, test_notes);
2655 BOOST_AUTO_TEST_CASE (verify_markers)
2657 verify_markers_test (
2658 "build/test/verify_markers_all_correct",
2660 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2661 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2662 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2663 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2668 verify_markers_test (
2669 "build/test/verify_markers_missing_ffec",
2671 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2672 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2673 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2679 verify_markers_test (
2680 "build/test/verify_markers_missing_ffmc",
2682 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2683 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2684 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2687 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2690 verify_markers_test (
2691 "build/test/verify_markers_missing_ffoc",
2693 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2694 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2695 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2698 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2701 verify_markers_test (
2702 "build/test/verify_markers_missing_lfoc",
2704 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2705 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2706 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2709 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2712 verify_markers_test (
2713 "build/test/verify_markers_incorrect_ffoc",
2715 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2716 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2717 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2718 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2721 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2724 verify_markers_test (
2725 "build/test/verify_markers_incorrect_lfoc",
2727 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2728 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2729 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2730 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2733 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2738 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2740 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2741 prepare_directory (dir);
2742 auto dcp = make_simple (dir);
2743 auto cpl = dcp->cpls()[0];
2744 cpl->unset_version_number();
2747 check_verify_result({dir}, {}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2751 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2753 path dir = "build/test/verify_missing_extension_metadata1";
2754 auto dcp = make_simple (dir);
2757 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2758 auto cpl = dcp->cpls()[0];
2761 Editor e (cpl->file().get());
2762 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2765 check_verify_result (
2769 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2770 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2775 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2777 path dir = "build/test/verify_missing_extension_metadata2";
2778 auto dcp = make_simple (dir);
2781 auto cpl = dcp->cpls()[0];
2784 Editor e (cpl->file().get());
2785 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2788 check_verify_result (
2792 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2798 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2800 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2801 auto dcp = make_simple (dir);
2804 auto const cpl = dcp->cpls()[0];
2807 Editor e (cpl->file().get());
2808 e.replace ("<meta:Name>A", "<meta:NameX>A");
2809 e.replace ("n</meta:Name>", "n</meta:NameX>");
2812 check_verify_result (
2816 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2817 { 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 },
2818 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2823 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2825 path dir = "build/test/verify_invalid_extension_metadata1";
2826 auto dcp = make_simple (dir);
2829 auto cpl = dcp->cpls()[0];
2832 Editor e (cpl->file().get());
2833 e.replace ("Application", "Fred");
2836 check_verify_result (
2840 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2841 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2846 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2848 path dir = "build/test/verify_invalid_extension_metadata2";
2849 auto dcp = make_simple (dir);
2852 auto cpl = dcp->cpls()[0];
2855 Editor e (cpl->file().get());
2856 e.replace ("DCP Constraints Profile", "Fred");
2859 check_verify_result (
2863 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2869 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2871 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2872 auto dcp = make_simple (dir);
2875 auto const cpl = dcp->cpls()[0];
2878 Editor e (cpl->file().get());
2879 e.replace ("<meta:Value>", "<meta:ValueX>");
2880 e.replace ("</meta:Value>", "</meta:ValueX>");
2883 check_verify_result (
2887 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2888 { 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 },
2889 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2894 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2896 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2897 auto dcp = make_simple (dir);
2900 auto const cpl = dcp->cpls()[0];
2903 Editor e (cpl->file().get());
2904 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2907 check_verify_result (
2911 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2912 { 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() },
2917 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2919 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2920 auto dcp = make_simple (dir);
2923 auto const cpl = dcp->cpls()[0];
2926 Editor e (cpl->file().get());
2927 e.replace ("<meta:Property>", "<meta:PropertyX>");
2928 e.replace ("</meta:Property>", "</meta:PropertyX>");
2931 check_verify_result (
2935 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2936 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2937 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2942 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2944 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2945 auto dcp = make_simple (dir);
2948 auto const cpl = dcp->cpls()[0];
2951 Editor e (cpl->file().get());
2952 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2953 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2956 check_verify_result (
2960 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2961 { 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 },
2962 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2968 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2970 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2971 prepare_directory (dir);
2972 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2973 copy_file (i.path(), dir / i.path().filename());
2976 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml" );
2977 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
2981 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2984 check_verify_result (
2988 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id(), canonical(cpl) },
2989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl), },
2990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2992 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2993 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2994 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id(), canonical(cpl) }
3000 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
3002 path dir = "build/test/unsigned_pkl_with_encrypted_content";
3003 prepare_directory (dir);
3004 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3005 copy_file (i.path(), dir / i.path().filename());
3008 path const cpl = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
3009 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
3012 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3015 check_verify_result (
3019 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) },
3020 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3021 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3022 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3023 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3024 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
3025 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) },
3030 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3032 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3033 prepare_directory (dir);
3034 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3035 copy_file (i.path(), dir / i.path().filename());
3039 Editor e (dir / dcp_test1_pkl());
3040 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3043 check_verify_result({dir}, {}, {});
3047 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3049 path dir ("build/test/verify_must_not_be_partially_encrypted");
3050 prepare_directory (dir);
3054 auto signer = make_shared<dcp::CertificateChain>();
3055 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3056 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3057 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3058 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3060 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3064 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3067 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3068 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3069 for (int i = 0; i < 24; ++i) {
3070 writer->write (j2c.data(), j2c.size());
3072 writer->finalize ();
3074 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3076 auto reel = make_shared<dcp::Reel>(
3077 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3078 make_shared<dcp::ReelSoundAsset>(ms, 0)
3081 reel->add (simple_markers());
3085 cpl->set_content_version (
3086 {"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"}
3088 cpl->set_annotation_text ("A Test DCP");
3089 cpl->set_issuer ("OpenDCP 0.0.25");
3090 cpl->set_creator ("OpenDCP 0.0.25");
3091 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3092 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3093 cpl->set_main_sound_sample_rate (48000);
3094 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3095 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3096 cpl->set_version_number (1);
3100 d.set_issuer("OpenDCP 0.0.25");
3101 d.set_creator("OpenDCP 0.0.25");
3102 d.set_issue_date("2012-07-17T04:45:18+00:00");
3103 d.set_annotation_text("A Test DCP");
3104 d.write_xml(signer);
3106 check_verify_result (
3110 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3115 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3117 vector<dcp::VerificationNote> notes;
3118 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"));
3119 auto reader = picture.start_read ();
3120 auto frame = reader->get_frame (0);
3121 verify_j2k(frame, 0, 0, 24, notes);
3122 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3126 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3128 vector<dcp::VerificationNote> notes;
3129 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3130 auto reader = picture.start_read ();
3131 auto frame = reader->get_frame (0);
3132 verify_j2k(frame, 0, 0, 24, notes);
3133 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3137 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3139 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3140 prepare_directory (dir);
3141 auto dcp = make_simple (dir);
3143 vector<dcp::VerificationNote> notes;
3144 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3145 auto reader = picture.start_read ();
3146 auto frame = reader->get_frame (0);
3147 verify_j2k(frame, 0, 0, 24, notes);
3148 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3152 /** Check that ResourceID and the XML ID being different is spotted */
3153 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3155 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3156 prepare_directory (dir);
3158 ASDCP::WriterInfo writer_info;
3159 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3162 auto mxf_id = dcp::make_uuid ();
3163 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3164 BOOST_REQUIRE (c == Kumu::UUID_Length);
3166 auto resource_id = dcp::make_uuid ();
3167 ASDCP::TimedText::TimedTextDescriptor descriptor;
3168 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3169 DCP_ASSERT (c == Kumu::UUID_Length);
3171 auto xml_id = dcp::make_uuid ();
3172 ASDCP::TimedText::MXFWriter writer;
3173 auto subs_mxf = dir / "subs.mxf";
3174 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3175 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3176 writer.WriteTimedTextResource (dcp::String::compose(
3177 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3178 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3179 "<Id>urn:uuid:%1</Id>"
3180 "<ContentTitleText>Content</ContentTitleText>"
3181 "<AnnotationText>Annotation</AnnotationText>"
3182 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3183 "<ReelNumber>1</ReelNumber>"
3184 "<Language>en-US</Language>"
3185 "<EditRate>25 1</EditRate>"
3186 "<TimeCodeRate>25</TimeCodeRate>"
3187 "<StartTime>00:00:00:00</StartTime>"
3188 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3190 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3191 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3192 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3201 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3202 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3204 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3206 check_verify_result (
3210 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3211 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3212 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3213 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3218 /** Check that ResourceID and the MXF ID being the same is spotted */
3219 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3221 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3222 prepare_directory (dir);
3224 ASDCP::WriterInfo writer_info;
3225 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3228 auto mxf_id = dcp::make_uuid ();
3229 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3230 BOOST_REQUIRE (c == Kumu::UUID_Length);
3232 auto resource_id = mxf_id;
3233 ASDCP::TimedText::TimedTextDescriptor descriptor;
3234 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3235 DCP_ASSERT (c == Kumu::UUID_Length);
3237 auto xml_id = resource_id;
3238 ASDCP::TimedText::MXFWriter writer;
3239 auto subs_mxf = dir / "subs.mxf";
3240 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3241 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3242 writer.WriteTimedTextResource (dcp::String::compose(
3243 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3244 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3245 "<Id>urn:uuid:%1</Id>"
3246 "<ContentTitleText>Content</ContentTitleText>"
3247 "<AnnotationText>Annotation</AnnotationText>"
3248 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3249 "<ReelNumber>1</ReelNumber>"
3250 "<Language>en-US</Language>"
3251 "<EditRate>25 1</EditRate>"
3252 "<TimeCodeRate>25</TimeCodeRate>"
3253 "<StartTime>00:00:00:00</StartTime>"
3254 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3256 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3257 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3258 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3267 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3268 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3270 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3272 check_verify_result (
3276 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3277 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3278 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3279 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3280 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3285 /** Check a DCP with a 3D asset marked as 2D */
3286 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3288 check_verify_result (
3289 { private_test / "data" / "xm" },
3293 dcp::VerificationNote::Type::WARNING,
3294 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3297 dcp::VerificationNote::Type::BV21_ERROR,
3298 dcp::VerificationNote::Code::INVALID_STANDARD
3305 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3307 path dir = "build/test/verify_unexpected_things_in_main_markers";
3308 prepare_directory (dir);
3309 auto dcp = make_simple (dir, 1, 24);
3313 Editor e (find_cpl(dir));
3315 " <IntrinsicDuration>24</IntrinsicDuration>",
3316 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3320 dcp::CPL cpl (find_cpl(dir));
3322 check_verify_result (
3326 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3327 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3328 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3333 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3335 path dir = "build/test/verify_invalid_content_kind";
3336 prepare_directory (dir);
3337 auto dcp = make_simple (dir, 1, 24);
3341 Editor e(find_cpl(dir));
3342 e.replace("trailer", "trip");
3345 dcp::CPL cpl (find_cpl(dir));
3347 check_verify_result (
3351 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3352 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3358 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3360 path dir = "build/test/verify_valid_content_kind";
3361 prepare_directory (dir);
3362 auto dcp = make_simple (dir, 1, 24);
3366 Editor e(find_cpl(dir));
3367 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3370 dcp::CPL cpl (find_cpl(dir));
3372 check_verify_result (
3376 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3382 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3384 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3385 prepare_directory(dir);
3386 auto dcp = make_simple(dir, 1, 24);
3389 auto constexpr area = "<meta:MainPictureActiveArea>";
3392 Editor e(find_cpl(dir));
3393 e.delete_lines_after(area, 2);
3394 e.insert(area, "<meta:Height>4080</meta:Height>");
3395 e.insert(area, "<meta:Width>1997</meta:Width>");
3398 dcp::PKL pkl(find_pkl(dir));
3399 dcp::CPL cpl(find_cpl(dir));
3401 check_verify_result(
3405 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3406 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3407 { 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)) },
3412 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3414 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3415 prepare_directory(dir);
3416 auto dcp = make_simple(dir, 1, 24);
3419 auto constexpr area = "<meta:MainPictureActiveArea>";
3422 Editor e(find_cpl(dir));
3423 e.delete_lines_after(area, 2);
3424 e.insert(area, "<meta:Height>5125</meta:Height>");
3425 e.insert(area, "<meta:Width>9900</meta:Width>");
3428 dcp::PKL pkl(find_pkl(dir));
3429 dcp::CPL cpl(find_cpl(dir));
3431 check_verify_result(
3435 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3436 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3437 { 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)) },
3438 { 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)) },
3443 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3447 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3448 prepare_directory(dir);
3449 auto dcp = make_simple(dir, 1, 24);
3453 Editor e(find_pkl(dir));
3454 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3457 dcp::PKL pkl(find_pkl(dir));
3459 check_verify_result(
3463 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3468 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3472 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3473 prepare_directory(dir);
3474 auto dcp = make_simple(dir, 1, 24);
3478 Editor e(find_asset_map(dir));
3479 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3482 dcp::PKL pkl(find_pkl(dir));
3483 dcp::AssetMap asset_map(find_asset_map(dir));
3485 check_verify_result(
3489 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3490 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3495 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3497 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3499 dcp::MXFMetadata mxf_meta;
3500 mxf_meta.company_name = "OpenDCP";
3501 mxf_meta.product_name = "OpenDCP";
3502 mxf_meta.product_version = "0.0.25";
3504 auto constexpr sample_rate = 48000;
3505 auto constexpr frames = 240;
3507 boost::filesystem::remove_all(path);
3508 boost::filesystem::create_directories(path);
3509 auto dcp = make_shared<dcp::DCP>(path);
3510 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3511 cpl->set_annotation_text("hello");
3512 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3513 cpl->set_main_sound_sample_rate(sample_rate);
3514 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3515 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3516 cpl->set_version_number(1);
3520 /* Reel with 2 channels of audio */
3522 auto mp = simple_picture(path, "1", frames, {});
3523 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3525 auto reel = make_shared<dcp::Reel>(
3526 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3527 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3530 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3531 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3538 /* Reel with 6 channels of audio */
3540 auto mp = simple_picture(path, "2", frames, {});
3541 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3543 auto reel = make_shared<dcp::Reel>(
3544 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3545 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3548 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3549 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3556 dcp->set_annotation_text("hello");
3559 check_verify_result(
3563 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3568 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3570 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3572 dcp::MXFMetadata mxf_meta;
3573 mxf_meta.company_name = "OpenDCP";
3574 mxf_meta.product_name = "OpenDCP";
3575 mxf_meta.product_version = "0.0.25";
3577 auto constexpr sample_rate = 48000;
3578 auto constexpr frames = 240;
3580 boost::filesystem::remove_all(path);
3581 boost::filesystem::create_directories(path);
3582 auto dcp = make_shared<dcp::DCP>(path);
3583 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3584 cpl->set_annotation_text("hello");
3585 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3586 cpl->set_main_sound_sample_rate(sample_rate);
3587 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3588 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3589 cpl->set_version_number(1);
3591 auto mp = simple_picture(path, "1", frames, {});
3592 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3594 auto reel = make_shared<dcp::Reel>(
3595 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3596 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3599 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3600 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3601 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3607 dcp->set_annotation_text("hello");
3610 check_verify_result(
3614 { 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)) },
3619 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3621 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3622 auto constexpr video_frames = 24;
3623 auto constexpr sample_rate = 48000;
3625 boost::filesystem::remove_all(path);
3626 boost::filesystem::create_directories(path);
3628 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3629 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3631 dcp::Size const size(1998, 1080);
3632 auto image = make_shared<dcp::OpenJPEGImage>(size);
3633 boost::random::mt19937 rng(1);
3634 boost::random::uniform_int_distribution<> dist(0, 4095);
3635 for (int c = 0; c < 3; ++c) {
3636 for (int p = 0; p < (1998 * 1080); ++p) {
3637 image->data(c)[p] = dist(rng);
3640 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3641 for (int i = 0; i < 24; ++i) {
3642 picture_writer->write(j2c.data(), j2c.size());
3644 picture_writer->finalize();
3646 auto dcp = make_shared<dcp::DCP>(path);
3647 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3648 cpl->set_content_version(
3649 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3651 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3652 cpl->set_main_sound_sample_rate(sample_rate);
3653 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3654 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3655 cpl->set_version_number(1);
3657 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3659 auto reel = make_shared<dcp::Reel>(
3660 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3661 make_shared<dcp::ReelSoundAsset>(ms, 0)
3666 dcp->set_annotation_text("A Test DCP");
3669 vector<dcp::VerificationNote> expected;
3672 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") }
3675 int component_sizes[] = {
3681 for (auto frame = 0; frame < 24; frame++) {
3682 for (auto component = 0; component < 3; component++) {
3684 dcp::VerificationNote(
3685 dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE
3686 ).set_frame(frame).set_component(component).set_size(component_sizes[component])
3692 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC }
3696 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
3699 check_verify_result({ path }, {}, expected);
3703 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3705 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3706 check_verify_result(
3710 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3712 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3713 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3714 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3715 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3720 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3722 path const dir("build/test/verify_missing_load_font");
3723 prepare_directory (dir);
3724 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3726 Editor editor(dir / "subs.xml");
3727 editor.delete_first_line_containing("LoadFont");
3729 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3730 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3731 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3733 check_verify_result (
3737 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3738 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3744 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3746 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3747 prepare_directory(dir);
3748 auto dcp = make_simple (dir, 1, 202);
3751 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3752 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3753 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3754 "<ContentTitleText>Content</ContentTitleText>"
3755 "<AnnotationText>Annotation</AnnotationText>"
3756 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3757 "<ReelNumber>1</ReelNumber>"
3758 "<EditRate>24 1</EditRate>"
3759 "<TimeCodeRate>24</TimeCodeRate>"
3760 "<StartTime>00:00:00:00</StartTime>"
3761 "<Language>de-DE</Language>"
3763 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3764 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3765 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3771 dcp::File xml_file(dir / "subs.xml", "w");
3772 BOOST_REQUIRE(xml_file);
3773 xml_file.write(xml.c_str(), xml.size(), 1);
3775 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3776 subs->write(dir / "subs.mxf");
3778 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3779 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3782 check_verify_result (
3786 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3791 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3793 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3794 boost::filesystem::remove_all(dir);
3796 auto dcp1 = make_simple(dir / "1");
3799 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3801 auto dcp2 = make_simple(dir / "2");
3803 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3805 boost::filesystem::remove(dir / "1" / "video.mxf");
3806 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3808 check_verify_result(
3812 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3817 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3819 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3820 boost::filesystem::remove_all(dir);
3822 auto dcp = make_simple(dir);
3823 BOOST_REQUIRE(dcp->cpls().size() == 1);
3824 auto cpl = dcp->cpls()[0];
3825 cpl->set_content_version(dcp::ContentVersion(""));
3828 check_verify_result(
3832 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())
3837 /** Check that we don't get any strange errors when verifying encrypted DCPs (DoM #2659) */
3838 BOOST_AUTO_TEST_CASE(verify_encrypted_smpte_dcp)
3840 auto const dir = path("build/test/verify_encrypted_smpte_dcp");
3842 auto key_id = dcp::make_uuid();
3843 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset>(dir, {{ 4 * 24, 5 * 24 }}, key, key_id);
3845 dcp::DecryptedKDM kdm(dcp::LocalTime(), dcp::LocalTime(), "", "", "");
3846 kdm.add_key(dcp::DecryptedKDMKey(string{"MDIK"}, key_id, key, cpl->id(), dcp::Standard::SMPTE));
3848 path const pkl_file = find_file(dir, "pkl_");
3849 path const cpl_file = find_file(dir, "cpl_");
3851 check_verify_result(
3855 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl_file) },
3856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl->id(), canonical(cpl_file) },
3857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, filename_to_id(pkl_file.filename()), canonical(pkl_file) }