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::VerificationNote> test_notes)
202 auto notes = dcp::verify({dir}, &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 (
349 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
350 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
355 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
357 using namespace boost::filesystem;
359 auto dir = setup (1, "mismatched_picture_sound_hashes");
362 Editor e (dir / dcp_test1_pkl());
363 e.replace ("<Hash>", "<Hash>x");
366 check_verify_result (
369 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl()) },
370 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
371 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
372 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 28 },
373 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 12 },
374 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl()), 20 },
379 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
381 auto dir = setup (1, "failed_read_content_kind");
384 Editor e (dir / dcp_test1_cpl());
385 e.replace ("<ContentKind>", "<ContentKind>x");
388 check_verify_result (
391 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id(), canonical(dir / dcp_test1_cpl()) },
392 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
401 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl());
409 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl());
415 asset_map (string suffix)
417 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
421 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
423 check_verify_result_after_replace (
424 "invalid_picture_frame_rate", &cpl,
425 "<FrameRate>24 1", "<FrameRate>99 1",
426 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
427 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
431 BOOST_AUTO_TEST_CASE (verify_missing_asset)
433 auto dir = setup (1, "missing_asset");
434 remove (dir / "video.mxf");
435 check_verify_result (
438 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
443 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
445 check_verify_result_after_replace (
446 "empty_asset_path", &asset_map,
447 "<Path>video.mxf</Path>", "<Path></Path>",
448 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
453 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
455 check_verify_result_after_replace (
456 "mismatched_standard", &cpl,
457 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
458 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
459 dcp::VerificationNote::Code::INVALID_XML,
460 dcp::VerificationNote::Code::INVALID_XML,
461 dcp::VerificationNote::Code::INVALID_XML,
462 dcp::VerificationNote::Code::INVALID_XML,
463 dcp::VerificationNote::Code::INVALID_XML,
464 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
469 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
471 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
472 check_verify_result_after_replace (
473 "invalid_xml_cpl_id", &cpl,
474 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
475 { dcp::VerificationNote::Code::INVALID_XML }
480 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
482 check_verify_result_after_replace (
483 "invalid_xml_issue_date", &cpl,
484 "<IssueDate>", "<IssueDate>x",
485 { dcp::VerificationNote::Code::INVALID_XML,
486 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
491 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
493 check_verify_result_after_replace (
494 "invalid_xml_pkl_id", &pkl,
495 "<Id>urn:uuid:" + dcp_test1_pkl_id().substr(0, 3),
496 "<Id>urn:uuid:x" + dcp_test1_pkl_id().substr(1, 2),
497 { dcp::VerificationNote::Code::INVALID_XML }
502 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
504 check_verify_result_after_replace (
505 "invalid_xml_asset_map_id", &asset_map,
506 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
507 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
508 { dcp::VerificationNote::Code::INVALID_XML }
513 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
516 auto dir = setup (3, "verify_invalid_standard");
517 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
519 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
520 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
521 path const assetmap_file = dir / "ASSETMAP";
523 auto st = stages.begin();
524 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
525 BOOST_REQUIRE (st->second);
526 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
528 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
529 BOOST_REQUIRE (st->second);
530 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
532 BOOST_CHECK_EQUAL (st->first, "Checking reel");
533 BOOST_REQUIRE (!st->second);
535 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
536 BOOST_REQUIRE (st->second);
537 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
539 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
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 sound asset hash");
544 BOOST_REQUIRE (st->second);
545 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
547 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
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 PKL");
552 BOOST_REQUIRE (st->second);
553 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
555 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
556 BOOST_REQUIRE (st->second);
557 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
559 BOOST_REQUIRE (st == stages.end());
561 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
562 auto i = notes.begin ();
563 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
564 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
566 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
567 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
570 /* DCP with a short asset */
571 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
573 auto dir = setup (8, "invalid_duration");
577 BOOST_REQUIRE(dcp.cpls().size() == 1);
578 auto cpl = dcp.cpls()[0];
580 check_verify_result (
583 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
584 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
585 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
587 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
588 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") },
589 dcp::VerificationNote(
590 dcp::VerificationNote::Type::WARNING,
591 dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT,
593 ).set_id("d74fda30-d5f4-4c5f-870f-ebc089d97eb7")
600 dcp_from_frame (dcp::ArrayData const& frame, path dir)
602 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
603 create_directories (dir);
604 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
605 for (int i = 0; i < 24; ++i) {
606 writer->write (frame.data(), frame.size());
610 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
611 return write_dcp_with_single_asset (dir, reel_asset);
615 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
617 int const too_big = 1302083 * 2;
619 /* Compress a black image */
620 auto image = black_image ();
621 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
622 BOOST_REQUIRE (frame.size() < too_big);
624 /* Place it in a bigger block with some zero padding at the end */
625 dcp::ArrayData oversized_frame(too_big);
626 memcpy (oversized_frame.data(), frame.data(), frame.size());
627 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
629 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
630 prepare_directory (dir);
631 auto cpl = dcp_from_frame (oversized_frame, dir);
633 check_verify_result (
636 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
637 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
638 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
643 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
645 int const nearly_too_big = 1302083 * 0.98;
647 /* Compress a black image */
648 auto image = black_image ();
649 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
650 BOOST_REQUIRE (frame.size() < nearly_too_big);
652 /* Place it in a bigger block with some zero padding at the end */
653 dcp::ArrayData oversized_frame(nearly_too_big);
654 memcpy (oversized_frame.data(), frame.data(), frame.size());
655 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
657 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
658 prepare_directory (dir);
659 auto cpl = dcp_from_frame (oversized_frame, dir);
661 check_verify_result (
664 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
665 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
666 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
671 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
673 /* Compress a black image */
674 auto image = black_image ();
675 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
676 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
678 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
679 prepare_directory (dir);
680 auto cpl = dcp_from_frame (frame, dir);
682 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
686 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
688 path const dir("build/test/verify_valid_interop_subtitles");
689 prepare_directory (dir);
690 copy_file ("test/data/subs1.xml", dir / "subs.xml");
691 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
692 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
693 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
695 check_verify_result (
697 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
698 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
703 BOOST_AUTO_TEST_CASE(verify_catch_missing_font_file_with_interop_ccap)
705 path const dir("build/test/verify_catch_missing_font_file_with_interop_ccap");
706 prepare_directory(dir);
707 copy_file("test/data/subs1.xml", dir / "ccap.xml");
708 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "ccap.xml");
709 auto reel_asset = make_shared<dcp::ReelInteropClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
710 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
712 check_verify_result (
714 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
720 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
722 using namespace boost::filesystem;
724 path const dir("build/test/verify_invalid_interop_subtitles");
725 prepare_directory (dir);
726 copy_file ("test/data/subs1.xml", dir / "subs.xml");
727 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
728 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
729 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
732 Editor e (dir / "subs.xml");
733 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
736 check_verify_result (
739 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
740 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
742 dcp::VerificationNote::Type::ERROR,
743 dcp::VerificationNote::Code::INVALID_XML,
744 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
748 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
753 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
755 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
756 prepare_directory(dir);
757 copy_file("test/data/subs4.xml", dir / "subs.xml");
758 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
759 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
760 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
762 check_verify_result (
765 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
766 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
767 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
773 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
775 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
776 prepare_directory(dir);
777 copy_file("test/data/subs5.xml", dir / "subs.xml");
778 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
779 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
780 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
782 check_verify_result (
785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
786 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
792 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
794 path const dir("build/test/verify_valid_smpte_subtitles");
795 prepare_directory (dir);
796 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
797 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
798 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
799 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
804 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
805 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
806 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
811 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
813 using namespace boost::filesystem;
815 path const dir("build/test/verify_invalid_smpte_subtitles");
816 prepare_directory (dir);
817 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
818 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
819 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
820 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
821 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
823 check_verify_result (
826 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
828 dcp::VerificationNote::Type::ERROR,
829 dcp::VerificationNote::Code::INVALID_XML,
830 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
834 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
836 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
837 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
842 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
844 path const dir("build/test/verify_empty_text_node_in_subtitles");
845 prepare_directory (dir);
846 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
847 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
848 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
849 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
851 check_verify_result (
854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
855 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
858 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
859 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
864 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
865 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
867 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
868 prepare_directory (dir);
869 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
870 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
871 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
872 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
874 check_verify_result (
877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
878 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
883 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
884 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
886 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
887 prepare_directory (dir);
888 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
889 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
890 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
891 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
893 check_verify_result (
896 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
898 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
899 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
904 BOOST_AUTO_TEST_CASE (verify_external_asset)
906 path const ov_dir("build/test/verify_external_asset");
907 prepare_directory (ov_dir);
909 auto image = black_image ();
910 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
911 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
912 dcp_from_frame (frame, ov_dir);
914 dcp::DCP ov (ov_dir);
917 path const vf_dir("build/test/verify_external_asset_vf");
918 prepare_directory (vf_dir);
920 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
921 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
923 check_verify_result (
926 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
932 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
934 path const dir("build/test/verify_valid_cpl_metadata");
935 prepare_directory (dir);
937 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
938 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
939 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
941 auto reel = make_shared<dcp::Reel>();
942 reel->add (reel_asset);
944 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
945 reel->add (simple_markers(16 * 24));
947 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
949 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
950 cpl->set_main_sound_sample_rate (48000);
951 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
952 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
953 cpl->set_version_number (1);
957 dcp.set_annotation_text("hello");
963 find_prefix(path dir, string prefix)
965 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
966 return boost::starts_with(p.filename().string(), prefix);
969 BOOST_REQUIRE(iter != directory_iterator());
974 path find_cpl (path dir)
976 return find_prefix(dir, "cpl_");
983 return find_prefix(dir, "pkl_");
988 find_asset_map(path dir)
990 return find_prefix(dir, "ASSETMAP");
994 /* DCP with invalid CompositionMetadataAsset */
995 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
997 using namespace boost::filesystem;
999 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1000 prepare_directory (dir);
1002 auto reel = make_shared<dcp::Reel>();
1003 reel->add (black_picture_asset(dir));
1004 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1006 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1007 cpl->set_main_sound_sample_rate (48000);
1008 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1009 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1010 cpl->set_version_number (1);
1012 reel->add (simple_markers());
1016 dcp.set_annotation_text("hello");
1020 Editor e (find_cpl(dir));
1021 e.replace ("MainSound", "MainSoundX");
1024 check_verify_result (
1027 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1028 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1030 dcp::VerificationNote::Type::ERROR,
1031 dcp::VerificationNote::Code::INVALID_XML,
1032 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1033 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1034 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1035 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1036 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1037 "ExtensionMetadataList?,)'"),
1038 canonical(cpl->file().get()),
1041 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1046 /* DCP with invalid CompositionMetadataAsset */
1047 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1049 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1050 prepare_directory (dir);
1052 auto reel = make_shared<dcp::Reel>();
1053 reel->add (black_picture_asset(dir));
1054 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1056 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1057 cpl->set_main_sound_sample_rate (48000);
1058 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1059 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1063 dcp.set_annotation_text("hello");
1067 Editor e (find_cpl(dir));
1068 e.replace ("meta:Width", "meta:WidthX");
1071 check_verify_result (
1073 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1078 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1080 path const dir("build/test/verify_invalid_language1");
1081 prepare_directory (dir);
1082 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1083 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1084 asset->_language = "wrong-andbad";
1085 asset->write (dir / "subs.mxf");
1086 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1087 reel_asset->_language = "badlang";
1088 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1090 check_verify_result (
1093 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1094 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1095 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1100 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1101 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1103 path const dir("build/test/verify_invalid_language2");
1104 prepare_directory (dir);
1105 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1106 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1107 asset->_language = "wrong-andbad";
1108 asset->write (dir / "subs.mxf");
1109 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1110 reel_asset->_language = "badlang";
1111 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1113 check_verify_result (
1116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1123 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1124 * the release territory.
1126 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1128 path const dir("build/test/verify_invalid_language3");
1129 prepare_directory (dir);
1131 auto picture = simple_picture (dir, "foo");
1132 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1133 auto reel = make_shared<dcp::Reel>();
1134 reel->add (reel_picture);
1135 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1136 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1137 reel->add (reel_sound);
1138 reel->add (simple_markers());
1140 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1142 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1143 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1144 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1145 cpl->set_main_sound_sample_rate (48000);
1146 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1147 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1148 cpl->set_version_number (1);
1149 cpl->_release_territory = "fred-jim";
1150 auto dcp = make_shared<dcp::DCP>(dir);
1152 dcp->set_annotation_text("hello");
1155 check_verify_result (
1158 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1159 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1160 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1161 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1167 vector<dcp::VerificationNote>
1168 check_picture_size (int width, int height, int frame_rate, bool three_d)
1170 using namespace boost::filesystem;
1172 path dcp_path = "build/test/verify_picture_test";
1173 prepare_directory (dcp_path);
1175 shared_ptr<dcp::PictureAsset> mp;
1177 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1179 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1181 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1183 auto image = black_image (dcp::Size(width, height));
1184 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1185 int const length = three_d ? frame_rate * 2 : frame_rate;
1186 for (int i = 0; i < length; ++i) {
1187 picture_writer->write (j2c.data(), j2c.size());
1189 picture_writer->finalize ();
1191 auto d = make_shared<dcp::DCP>(dcp_path);
1192 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1193 cpl->set_annotation_text ("A Test DCP");
1194 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1195 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1196 cpl->set_main_sound_sample_rate (48000);
1197 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1198 cpl->set_main_picture_active_area(dcp::Size(width, height));
1199 cpl->set_version_number (1);
1201 auto reel = make_shared<dcp::Reel>();
1204 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1206 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1209 reel->add (simple_markers(frame_rate));
1214 d->set_annotation_text("A Test DCP");
1217 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1223 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1225 auto notes = check_picture_size(width, height, frame_rate, three_d);
1226 BOOST_CHECK_EQUAL (notes.size(), 0U);
1232 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1234 auto notes = check_picture_size(width, height, frame_rate, three_d);
1235 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1236 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1237 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1243 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1245 auto notes = check_picture_size(width, height, frame_rate, three_d);
1246 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1247 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1248 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1254 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1256 auto notes = check_picture_size(width, height, frame_rate, three_d);
1257 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1258 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1259 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1263 BOOST_AUTO_TEST_CASE (verify_picture_size)
1265 using namespace boost::filesystem;
1268 check_picture_size_ok (2048, 858, 24, false);
1269 check_picture_size_ok (2048, 858, 25, false);
1270 check_picture_size_ok (2048, 858, 48, false);
1271 check_picture_size_ok (2048, 858, 24, true);
1272 check_picture_size_ok (2048, 858, 25, true);
1273 check_picture_size_ok (2048, 858, 48, true);
1276 check_picture_size_ok (1998, 1080, 24, false);
1277 check_picture_size_ok (1998, 1080, 25, false);
1278 check_picture_size_ok (1998, 1080, 48, false);
1279 check_picture_size_ok (1998, 1080, 24, true);
1280 check_picture_size_ok (1998, 1080, 25, true);
1281 check_picture_size_ok (1998, 1080, 48, true);
1284 check_picture_size_ok (4096, 1716, 24, false);
1287 check_picture_size_ok (3996, 2160, 24, false);
1289 /* Bad frame size */
1290 check_picture_size_bad_frame_size (2050, 858, 24, false);
1291 check_picture_size_bad_frame_size (2048, 658, 25, false);
1292 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1293 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1295 /* Bad 2K frame rate */
1296 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1297 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1298 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1300 /* Bad 4K frame rate */
1301 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1302 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1305 auto notes = check_picture_size(3996, 2160, 24, true);
1306 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1307 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1308 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1314 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")
1317 std::make_shared<dcp::SubtitleString>(
1325 dcp::Time(start_frame, 24, 24),
1326 dcp::Time(end_frame, 24, 24),
1328 dcp::HAlign::CENTER,
1332 dcp::Direction::LTR,
1344 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1346 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1347 prepare_directory (dir);
1349 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1350 for (int i = 0; i < 2048; ++i) {
1351 add_test_subtitle (asset, i * 24, i * 24 + 20);
1354 asset->set_language (dcp::LanguageTag("de-DE"));
1355 asset->write (dir / "subs.mxf");
1356 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1357 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1359 check_verify_result (
1362 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1364 dcp::VerificationNote::Type::BV21_ERROR,
1365 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1367 canonical(dir / "subs.mxf")
1369 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1370 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1376 shared_ptr<dcp::SMPTESubtitleAsset>
1377 make_large_subtitle_asset (path font_file)
1379 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1380 dcp::ArrayData big_fake_font(1024 * 1024);
1381 big_fake_font.write (font_file);
1382 for (int i = 0; i < 116; ++i) {
1383 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1391 verify_timed_text_asset_too_large (string name)
1393 auto const dir = path("build/test") / name;
1394 prepare_directory (dir);
1395 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1396 add_test_subtitle (asset, 0, 240);
1397 asset->set_language (dcp::LanguageTag("de-DE"));
1398 asset->write (dir / "subs.mxf");
1400 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1401 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1403 check_verify_result (
1406 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1407 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1408 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1409 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1410 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1415 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1417 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1418 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1422 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1424 path dir = "build/test/verify_missing_subtitle_language";
1425 prepare_directory (dir);
1426 auto dcp = make_simple (dir, 1, 106);
1429 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1430 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1431 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1432 "<ContentTitleText>Content</ContentTitleText>"
1433 "<AnnotationText>Annotation</AnnotationText>"
1434 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1435 "<ReelNumber>1</ReelNumber>"
1436 "<EditRate>24 1</EditRate>"
1437 "<TimeCodeRate>24</TimeCodeRate>"
1438 "<StartTime>00:00:00:00</StartTime>"
1439 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1441 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1442 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1443 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1449 dcp::File xml_file(dir / "subs.xml", "w");
1450 BOOST_REQUIRE (xml_file);
1451 xml_file.write(xml.c_str(), xml.size(), 1);
1453 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1454 subs->write (dir / "subs.mxf");
1456 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1457 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1460 check_verify_result (
1463 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1464 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1469 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1471 path path ("build/test/verify_mismatched_subtitle_languages");
1472 auto constexpr reel_length = 192;
1473 auto dcp = make_simple (path, 2, reel_length);
1474 auto cpl = dcp->cpls()[0];
1477 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1478 subs->set_language (dcp::LanguageTag("de-DE"));
1479 subs->add (simple_subtitle());
1481 subs->write (path / "subs1.mxf");
1482 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1483 cpl->reels()[0]->add(reel_subs);
1487 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1488 subs->set_language (dcp::LanguageTag("en-US"));
1489 subs->add (simple_subtitle());
1491 subs->write (path / "subs2.mxf");
1492 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1493 cpl->reels()[1]->add(reel_subs);
1498 check_verify_result (
1501 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1502 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1503 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1508 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1510 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1511 auto constexpr reel_length = 192;
1512 auto dcp = make_simple (path, 2, reel_length);
1513 auto cpl = dcp->cpls()[0];
1516 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1517 ccaps->set_language (dcp::LanguageTag("de-DE"));
1518 ccaps->add (simple_subtitle());
1520 ccaps->write (path / "subs1.mxf");
1521 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1522 cpl->reels()[0]->add(reel_ccaps);
1526 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1527 ccaps->set_language (dcp::LanguageTag("en-US"));
1528 ccaps->add (simple_subtitle());
1530 ccaps->write (path / "subs2.mxf");
1531 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1532 cpl->reels()[1]->add(reel_ccaps);
1537 check_verify_result (
1540 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1541 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1546 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1548 path dir = "build/test/verify_missing_subtitle_start_time";
1549 prepare_directory (dir);
1550 auto dcp = make_simple (dir, 1, 106);
1553 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1554 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1555 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1556 "<ContentTitleText>Content</ContentTitleText>"
1557 "<AnnotationText>Annotation</AnnotationText>"
1558 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1559 "<ReelNumber>1</ReelNumber>"
1560 "<Language>de-DE</Language>"
1561 "<EditRate>24 1</EditRate>"
1562 "<TimeCodeRate>24</TimeCodeRate>"
1563 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1565 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1566 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1567 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1573 dcp::File xml_file(dir / "subs.xml", "w");
1574 BOOST_REQUIRE (xml_file);
1575 xml_file.write(xml.c_str(), xml.size(), 1);
1577 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1578 subs->write (dir / "subs.mxf");
1580 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1581 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1584 check_verify_result (
1587 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1588 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1593 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1595 path dir = "build/test/verify_invalid_subtitle_start_time";
1596 prepare_directory (dir);
1597 auto dcp = make_simple (dir, 1, 106);
1600 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1601 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1602 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1603 "<ContentTitleText>Content</ContentTitleText>"
1604 "<AnnotationText>Annotation</AnnotationText>"
1605 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1606 "<ReelNumber>1</ReelNumber>"
1607 "<Language>de-DE</Language>"
1608 "<EditRate>24 1</EditRate>"
1609 "<TimeCodeRate>24</TimeCodeRate>"
1610 "<StartTime>00:00:02:00</StartTime>"
1611 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1613 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1614 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1615 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1621 dcp::File xml_file(dir / "subs.xml", "w");
1622 BOOST_REQUIRE (xml_file);
1623 xml_file.write(xml.c_str(), xml.size(), 1);
1625 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1626 subs->write (dir / "subs.mxf");
1628 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1629 dcp->cpls().front()->reels().front()->add(reel_subs);
1632 check_verify_result (
1635 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1636 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1644 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1647 , v_position(v_position_)
1655 dcp::VAlign v_align;
1661 shared_ptr<dcp::CPL>
1662 dcp_with_text (path dir, vector<TestText> subs)
1664 prepare_directory (dir);
1665 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1666 asset->set_start_time (dcp::Time());
1667 for (auto i: subs) {
1668 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1670 asset->set_language (dcp::LanguageTag("de-DE"));
1672 asset->write (dir / "subs.mxf");
1674 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1675 return write_dcp_with_single_asset (dir, reel_asset);
1680 shared_ptr<dcp::CPL>
1681 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1683 prepare_directory (dir);
1684 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1685 asset->set_start_time (dcp::Time());
1686 asset->set_language (dcp::LanguageTag("de-DE"));
1688 auto subs_mxf = dir / "subs.mxf";
1689 asset->write (subs_mxf);
1691 /* The call to write() puts the asset into the DCP correctly but it will have
1692 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1695 ASDCP::TimedText::MXFWriter writer;
1696 ASDCP::WriterInfo writer_info;
1697 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1699 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1700 DCP_ASSERT (c == Kumu::UUID_Length);
1701 ASDCP::TimedText::TimedTextDescriptor descriptor;
1702 descriptor.ContainerDuration = asset->intrinsic_duration();
1703 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1704 DCP_ASSERT (c == Kumu::UUID_Length);
1705 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1706 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1707 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1708 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1711 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1712 return write_dcp_with_single_asset (dir, reel_asset);
1716 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1718 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1719 /* Just too early */
1720 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1721 check_verify_result (
1724 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1731 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1733 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1734 /* Just late enough */
1735 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1736 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1740 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1742 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1743 prepare_directory (dir);
1745 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1746 asset1->set_start_time (dcp::Time());
1747 /* Just late enough */
1748 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1749 asset1->set_language (dcp::LanguageTag("de-DE"));
1751 asset1->write (dir / "subs1.mxf");
1752 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1753 auto reel1 = make_shared<dcp::Reel>();
1754 reel1->add (reel_asset1);
1755 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1756 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1757 reel1->add (markers1);
1759 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1760 asset2->set_start_time (dcp::Time());
1762 /* This would be too early on first reel but should be OK on the second */
1763 add_test_subtitle (asset2, 3, 4 * 24);
1764 asset2->set_language (dcp::LanguageTag("de-DE"));
1765 asset2->write (dir / "subs2.mxf");
1766 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1767 auto reel2 = make_shared<dcp::Reel>();
1768 reel2->add (reel_asset2);
1769 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1770 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1771 reel2->add (markers2);
1773 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1776 auto dcp = make_shared<dcp::DCP>(dir);
1778 dcp->set_annotation_text("hello");
1781 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1785 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1787 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1788 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1792 { 5 * 24 + 1, 6 * 24 },
1794 check_verify_result (
1797 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1803 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1805 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1806 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1810 { 5 * 24 + 16, 8 * 24 },
1812 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1816 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1818 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1819 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1820 check_verify_result (
1823 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1824 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1829 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1831 auto const dir = path("build/test/verify_valid_subtitle_duration");
1832 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1833 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1837 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1839 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1840 prepare_directory (dir);
1841 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1842 asset->set_start_time (dcp::Time());
1843 add_test_subtitle (asset, 0, 4 * 24);
1845 asset->set_language (dcp::LanguageTag("de-DE"));
1846 asset->write (dir / "subs.mxf");
1848 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1849 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1862 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1864 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1865 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1868 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1869 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1870 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1871 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1873 check_verify_result (
1876 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1882 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1884 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1885 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1888 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1889 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1890 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1892 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1896 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1898 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1899 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1902 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1903 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1904 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1905 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1907 check_verify_result (
1910 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1916 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1918 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1919 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1922 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1923 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1924 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1925 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1927 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1931 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1933 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1934 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1937 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1939 check_verify_result (
1942 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1943 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1948 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1950 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1951 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1954 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1956 check_verify_result (
1959 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1960 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1965 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1967 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1968 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1971 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1972 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1973 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1974 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1976 check_verify_result (
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1985 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1987 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1988 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1991 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1992 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1993 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1995 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1999 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2001 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2002 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2005 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2006 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2007 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2008 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2010 check_verify_result (
2013 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2019 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2021 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2022 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2025 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2026 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2027 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2028 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2030 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2034 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2036 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2037 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2040 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2042 check_verify_result (
2045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2050 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2052 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2053 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2056 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2058 check_verify_result (
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2062 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2067 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2069 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2070 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2073 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2074 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2075 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2077 check_verify_result (
2080 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2085 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2087 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2088 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2091 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2092 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2093 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2095 check_verify_result (
2098 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2099 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2104 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2106 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2107 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2110 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2111 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2112 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2114 check_verify_result (
2117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2122 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2124 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2125 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2128 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2129 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2130 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2132 check_verify_result (
2135 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2140 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2142 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2143 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2144 check_verify_result (
2147 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2148 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2153 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2155 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2156 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2157 check_verify_result (
2160 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2166 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2168 path const dir("build/test/verify_invalid_sound_frame_rate");
2169 prepare_directory (dir);
2171 auto picture = simple_picture (dir, "foo");
2172 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2173 auto reel = make_shared<dcp::Reel>();
2174 reel->add (reel_picture);
2175 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2176 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2177 reel->add (reel_sound);
2178 reel->add (simple_markers());
2179 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2181 auto dcp = make_shared<dcp::DCP>(dir);
2183 dcp->set_annotation_text("hello");
2186 check_verify_result (
2189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2195 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2197 path const dir("build/test/verify_missing_cpl_annotation_text");
2198 auto dcp = make_simple (dir);
2201 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2203 auto const cpl = dcp->cpls()[0];
2206 BOOST_REQUIRE (cpl->file());
2207 Editor e(cpl->file().get());
2208 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2211 check_verify_result (
2214 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2215 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2220 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2222 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2223 auto dcp = make_simple (dir);
2226 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2227 auto const cpl = dcp->cpls()[0];
2230 BOOST_REQUIRE (cpl->file());
2231 Editor e(cpl->file().get());
2232 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2235 check_verify_result (
2238 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2239 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2244 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2246 path const dir("build/test/verify_mismatched_asset_duration");
2247 prepare_directory (dir);
2248 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2249 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2251 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2252 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2254 auto reel = make_shared<dcp::Reel>(
2255 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2256 make_shared<dcp::ReelSoundAsset>(ms, 0)
2259 reel->add (simple_markers());
2263 dcp->set_annotation_text("A Test DCP");
2266 check_verify_result (
2269 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2270 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2277 shared_ptr<dcp::CPL>
2278 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2280 prepare_directory (dir);
2281 auto dcp = make_shared<dcp::DCP>(dir);
2282 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2284 auto constexpr reel_length = 192;
2286 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2287 subs->set_language (dcp::LanguageTag("de-DE"));
2288 subs->set_start_time (dcp::Time());
2289 subs->add (simple_subtitle());
2291 subs->write (dir / "subs.mxf");
2292 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2294 auto reel1 = make_shared<dcp::Reel>(
2295 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2296 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2300 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2303 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2304 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2305 reel1->add (markers1);
2309 auto reel2 = make_shared<dcp::Reel>(
2310 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2311 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2315 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2318 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2319 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2320 reel2->add (markers2);
2325 dcp->set_annotation_text("A Test DCP");
2332 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2335 path dir ("build/test/missing_main_subtitle_from_some_reels");
2336 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2337 check_verify_result (
2340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2347 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2348 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2349 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2353 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2354 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2355 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2361 shared_ptr<dcp::CPL>
2362 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2364 prepare_directory (dir);
2365 auto dcp = make_shared<dcp::DCP>(dir);
2366 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2368 auto constexpr reel_length = 192;
2370 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2371 subs->set_language (dcp::LanguageTag("de-DE"));
2372 subs->set_start_time (dcp::Time());
2373 subs->add (simple_subtitle());
2375 subs->write (dir / "subs.mxf");
2377 auto reel1 = make_shared<dcp::Reel>(
2378 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2379 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2382 for (int i = 0; i < caps_in_reel1; ++i) {
2383 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2386 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2387 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2388 reel1->add (markers1);
2392 auto reel2 = make_shared<dcp::Reel>(
2393 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2394 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2397 for (int i = 0; i < caps_in_reel2; ++i) {
2398 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2401 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2402 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2403 reel2->add (markers2);
2408 dcp->set_annotation_text("A Test DCP");
2415 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2418 path dir ("build/test/mismatched_closed_caption_asset_counts");
2419 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2420 check_verify_result (
2423 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2429 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2430 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2431 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2435 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2436 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2437 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2444 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2446 prepare_directory (dir);
2447 auto dcp = make_shared<dcp::DCP>(dir);
2448 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2450 auto constexpr reel_length = 192;
2452 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2453 subs->set_language (dcp::LanguageTag("de-DE"));
2454 subs->set_start_time (dcp::Time());
2455 subs->add (simple_subtitle());
2457 subs->write (dir / "subs.mxf");
2458 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2461 auto reel = make_shared<dcp::Reel>(
2462 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2463 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2466 reel->add (reel_text);
2468 reel->add (simple_markers(reel_length));
2473 dcp->set_annotation_text("A Test DCP");
2476 check_verify_result (
2479 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2480 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2485 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2487 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2488 "build/test/verify_subtitle_entry_point_must_be_present",
2489 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2490 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2491 asset->unset_entry_point ();
2495 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2496 "build/test/verify_subtitle_entry_point_must_be_zero",
2497 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2498 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2499 asset->set_entry_point (4);
2503 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2504 "build/test/verify_closed_caption_entry_point_must_be_present",
2505 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2506 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2507 asset->unset_entry_point ();
2511 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2512 "build/test/verify_closed_caption_entry_point_must_be_zero",
2513 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2514 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2515 asset->set_entry_point (9);
2521 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2525 path const dir("build/test/verify_missing_hash");
2526 auto dcp = make_simple (dir);
2529 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2530 auto const cpl = dcp->cpls()[0];
2531 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2532 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2533 auto asset_id = cpl->reels()[0]->main_picture()->id();
2536 BOOST_REQUIRE (cpl->file());
2537 Editor e(cpl->file().get());
2538 e.delete_first_line_containing("<Hash>");
2541 check_verify_result (
2544 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2552 verify_markers_test (
2554 vector<pair<dcp::Marker, dcp::Time>> markers,
2555 vector<dcp::VerificationNote> test_notes
2558 auto dcp = make_simple (dir);
2559 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2560 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2561 for (auto const& i: markers) {
2562 markers_asset->set (i.first, i.second);
2564 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2567 check_verify_result ({dir}, test_notes);
2571 BOOST_AUTO_TEST_CASE (verify_markers)
2573 verify_markers_test (
2574 "build/test/verify_markers_all_correct",
2576 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2577 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2578 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2579 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2584 verify_markers_test (
2585 "build/test/verify_markers_missing_ffec",
2587 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2588 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2589 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2592 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2595 verify_markers_test (
2596 "build/test/verify_markers_missing_ffmc",
2598 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2599 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2600 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2603 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2606 verify_markers_test (
2607 "build/test/verify_markers_missing_ffoc",
2609 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2610 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2611 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2614 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2617 verify_markers_test (
2618 "build/test/verify_markers_missing_lfoc",
2620 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2621 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2622 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2625 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2628 verify_markers_test (
2629 "build/test/verify_markers_incorrect_ffoc",
2631 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2632 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2633 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2634 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2640 verify_markers_test (
2641 "build/test/verify_markers_incorrect_lfoc",
2643 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2644 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2645 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2646 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2649 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2654 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2656 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2657 prepare_directory (dir);
2658 auto dcp = make_simple (dir);
2659 auto cpl = dcp->cpls()[0];
2660 cpl->unset_version_number();
2663 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2667 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2669 path dir = "build/test/verify_missing_extension_metadata1";
2670 auto dcp = make_simple (dir);
2673 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2674 auto cpl = dcp->cpls()[0];
2677 Editor e (cpl->file().get());
2678 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2681 check_verify_result (
2684 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2690 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2692 path dir = "build/test/verify_missing_extension_metadata2";
2693 auto dcp = make_simple (dir);
2696 auto cpl = dcp->cpls()[0];
2699 Editor e (cpl->file().get());
2700 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2703 check_verify_result (
2706 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2707 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2712 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2714 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2715 auto dcp = make_simple (dir);
2718 auto const cpl = dcp->cpls()[0];
2721 Editor e (cpl->file().get());
2722 e.replace ("<meta:Name>A", "<meta:NameX>A");
2723 e.replace ("n</meta:Name>", "n</meta:NameX>");
2726 check_verify_result (
2729 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2730 { 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 },
2731 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2736 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2738 path dir = "build/test/verify_invalid_extension_metadata1";
2739 auto dcp = make_simple (dir);
2742 auto cpl = dcp->cpls()[0];
2745 Editor e (cpl->file().get());
2746 e.replace ("Application", "Fred");
2749 check_verify_result (
2752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2753 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2758 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2760 path dir = "build/test/verify_invalid_extension_metadata2";
2761 auto dcp = make_simple (dir);
2764 auto cpl = dcp->cpls()[0];
2767 Editor e (cpl->file().get());
2768 e.replace ("DCP Constraints Profile", "Fred");
2771 check_verify_result (
2774 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2775 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2780 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2782 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2783 auto dcp = make_simple (dir);
2786 auto const cpl = dcp->cpls()[0];
2789 Editor e (cpl->file().get());
2790 e.replace ("<meta:Value>", "<meta:ValueX>");
2791 e.replace ("</meta:Value>", "</meta:ValueX>");
2794 check_verify_result (
2797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2798 { 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 },
2799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2804 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2806 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2807 auto dcp = make_simple (dir);
2810 auto const cpl = dcp->cpls()[0];
2813 Editor e (cpl->file().get());
2814 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2817 check_verify_result (
2820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2821 { 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() },
2826 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2828 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2829 auto dcp = make_simple (dir);
2832 auto const cpl = dcp->cpls()[0];
2835 Editor e (cpl->file().get());
2836 e.replace ("<meta:Property>", "<meta:PropertyX>");
2837 e.replace ("</meta:Property>", "</meta:PropertyX>");
2840 check_verify_result (
2843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2850 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2852 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2853 auto dcp = make_simple (dir);
2856 auto const cpl = dcp->cpls()[0];
2859 Editor e (cpl->file().get());
2860 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2861 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2864 check_verify_result (
2867 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2868 { 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 },
2869 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2875 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2877 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2878 prepare_directory (dir);
2879 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2880 copy_file (i.path(), dir / i.path().filename());
2883 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml" );
2884 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
2888 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2891 check_verify_result (
2894 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id(), canonical(cpl) },
2895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl), },
2896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2898 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2899 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id(), canonical(cpl) }
2906 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2908 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2909 prepare_directory (dir);
2910 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2911 copy_file (i.path(), dir / i.path().filename());
2914 path const cpl = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
2915 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
2918 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2921 check_verify_result (
2924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) },
2925 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2926 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2927 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2928 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2929 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) },
2935 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2937 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2938 prepare_directory (dir);
2939 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2940 copy_file (i.path(), dir / i.path().filename());
2944 Editor e (dir / dcp_test1_pkl());
2945 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2948 check_verify_result ({dir}, {});
2952 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2954 path dir ("build/test/verify_must_not_be_partially_encrypted");
2955 prepare_directory (dir);
2959 auto signer = make_shared<dcp::CertificateChain>();
2960 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2961 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2962 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2963 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2965 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2969 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2972 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
2973 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2974 for (int i = 0; i < 24; ++i) {
2975 writer->write (j2c.data(), j2c.size());
2977 writer->finalize ();
2979 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2981 auto reel = make_shared<dcp::Reel>(
2982 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2983 make_shared<dcp::ReelSoundAsset>(ms, 0)
2986 reel->add (simple_markers());
2990 cpl->set_content_version (
2991 {"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"}
2993 cpl->set_annotation_text ("A Test DCP");
2994 cpl->set_issuer ("OpenDCP 0.0.25");
2995 cpl->set_creator ("OpenDCP 0.0.25");
2996 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2997 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
2998 cpl->set_main_sound_sample_rate (48000);
2999 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3000 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3001 cpl->set_version_number (1);
3005 d.set_issuer("OpenDCP 0.0.25");
3006 d.set_creator("OpenDCP 0.0.25");
3007 d.set_issue_date("2012-07-17T04:45:18+00:00");
3008 d.set_annotation_text("A Test DCP");
3009 d.write_xml(signer);
3011 check_verify_result (
3014 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3019 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3021 vector<dcp::VerificationNote> notes;
3022 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"));
3023 auto reader = picture.start_read ();
3024 auto frame = reader->get_frame (0);
3025 verify_j2k(frame, 0, 24, notes);
3026 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3030 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3032 vector<dcp::VerificationNote> notes;
3033 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3034 auto reader = picture.start_read ();
3035 auto frame = reader->get_frame (0);
3036 verify_j2k(frame, 0, 24, notes);
3037 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3041 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3043 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3044 prepare_directory (dir);
3045 auto dcp = make_simple (dir);
3047 vector<dcp::VerificationNote> notes;
3048 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3049 auto reader = picture.start_read ();
3050 auto frame = reader->get_frame (0);
3051 verify_j2k(frame, 0, 24, notes);
3052 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3056 /** Check that ResourceID and the XML ID being different is spotted */
3057 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3059 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3060 prepare_directory (dir);
3062 ASDCP::WriterInfo writer_info;
3063 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3066 auto mxf_id = dcp::make_uuid ();
3067 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3068 BOOST_REQUIRE (c == Kumu::UUID_Length);
3070 auto resource_id = dcp::make_uuid ();
3071 ASDCP::TimedText::TimedTextDescriptor descriptor;
3072 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3073 DCP_ASSERT (c == Kumu::UUID_Length);
3075 auto xml_id = dcp::make_uuid ();
3076 ASDCP::TimedText::MXFWriter writer;
3077 auto subs_mxf = dir / "subs.mxf";
3078 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3079 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3080 writer.WriteTimedTextResource (dcp::String::compose(
3081 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3082 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3083 "<Id>urn:uuid:%1</Id>"
3084 "<ContentTitleText>Content</ContentTitleText>"
3085 "<AnnotationText>Annotation</AnnotationText>"
3086 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3087 "<ReelNumber>1</ReelNumber>"
3088 "<Language>en-US</Language>"
3089 "<EditRate>25 1</EditRate>"
3090 "<TimeCodeRate>25</TimeCodeRate>"
3091 "<StartTime>00:00:00:00</StartTime>"
3092 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3094 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3095 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3096 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3105 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3106 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3108 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3110 check_verify_result (
3113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3115 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3121 /** Check that ResourceID and the MXF ID being the same is spotted */
3122 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3124 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3125 prepare_directory (dir);
3127 ASDCP::WriterInfo writer_info;
3128 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3131 auto mxf_id = dcp::make_uuid ();
3132 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3133 BOOST_REQUIRE (c == Kumu::UUID_Length);
3135 auto resource_id = mxf_id;
3136 ASDCP::TimedText::TimedTextDescriptor descriptor;
3137 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3138 DCP_ASSERT (c == Kumu::UUID_Length);
3140 auto xml_id = resource_id;
3141 ASDCP::TimedText::MXFWriter writer;
3142 auto subs_mxf = dir / "subs.mxf";
3143 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3144 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3145 writer.WriteTimedTextResource (dcp::String::compose(
3146 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3147 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3148 "<Id>urn:uuid:%1</Id>"
3149 "<ContentTitleText>Content</ContentTitleText>"
3150 "<AnnotationText>Annotation</AnnotationText>"
3151 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3152 "<ReelNumber>1</ReelNumber>"
3153 "<Language>en-US</Language>"
3154 "<EditRate>25 1</EditRate>"
3155 "<TimeCodeRate>25</TimeCodeRate>"
3156 "<StartTime>00:00:00:00</StartTime>"
3157 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3159 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3160 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3161 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3170 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3171 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3173 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3175 check_verify_result (
3178 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3179 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3180 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3181 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3182 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3187 /** Check a DCP with a 3D asset marked as 2D */
3188 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3190 check_verify_result (
3191 { private_test / "data" / "xm" },
3194 dcp::VerificationNote::Type::WARNING,
3195 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3198 dcp::VerificationNote::Type::BV21_ERROR,
3199 dcp::VerificationNote::Code::INVALID_STANDARD
3206 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3208 path dir = "build/test/verify_unexpected_things_in_main_markers";
3209 prepare_directory (dir);
3210 auto dcp = make_simple (dir, 1, 24);
3214 Editor e (find_cpl(dir));
3216 " <IntrinsicDuration>24</IntrinsicDuration>",
3217 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3221 dcp::CPL cpl (find_cpl(dir));
3223 check_verify_result (
3226 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3227 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3228 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3233 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3235 path dir = "build/test/verify_invalid_content_kind";
3236 prepare_directory (dir);
3237 auto dcp = make_simple (dir, 1, 24);
3241 Editor e(find_cpl(dir));
3242 e.replace("trailer", "trip");
3245 dcp::CPL cpl (find_cpl(dir));
3247 check_verify_result (
3250 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3251 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3257 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3259 path dir = "build/test/verify_valid_content_kind";
3260 prepare_directory (dir);
3261 auto dcp = make_simple (dir, 1, 24);
3265 Editor e(find_cpl(dir));
3266 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3269 dcp::CPL cpl (find_cpl(dir));
3271 check_verify_result (
3274 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3280 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3282 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3283 prepare_directory(dir);
3284 auto dcp = make_simple(dir, 1, 24);
3287 auto constexpr area = "<meta:MainPictureActiveArea>";
3290 Editor e(find_cpl(dir));
3291 e.delete_lines_after(area, 2);
3292 e.insert(area, "<meta:Height>4080</meta:Height>");
3293 e.insert(area, "<meta:Width>1997</meta:Width>");
3296 dcp::PKL pkl(find_pkl(dir));
3297 dcp::CPL cpl(find_cpl(dir));
3299 check_verify_result(
3302 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3303 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3304 { 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)) },
3309 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3311 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3312 prepare_directory(dir);
3313 auto dcp = make_simple(dir, 1, 24);
3316 auto constexpr area = "<meta:MainPictureActiveArea>";
3319 Editor e(find_cpl(dir));
3320 e.delete_lines_after(area, 2);
3321 e.insert(area, "<meta:Height>5125</meta:Height>");
3322 e.insert(area, "<meta:Width>9900</meta:Width>");
3325 dcp::PKL pkl(find_pkl(dir));
3326 dcp::CPL cpl(find_cpl(dir));
3328 check_verify_result(
3331 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3332 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3333 { 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)) },
3334 { 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)) },
3339 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3343 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3344 prepare_directory(dir);
3345 auto dcp = make_simple(dir, 1, 24);
3349 Editor e(find_pkl(dir));
3350 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3353 dcp::PKL pkl(find_pkl(dir));
3355 check_verify_result(
3358 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3363 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3367 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3368 prepare_directory(dir);
3369 auto dcp = make_simple(dir, 1, 24);
3373 Editor e(find_asset_map(dir));
3374 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3377 dcp::PKL pkl(find_pkl(dir));
3378 dcp::AssetMap asset_map(find_asset_map(dir));
3380 check_verify_result(
3383 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3384 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3389 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3391 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3393 dcp::MXFMetadata mxf_meta;
3394 mxf_meta.company_name = "OpenDCP";
3395 mxf_meta.product_name = "OpenDCP";
3396 mxf_meta.product_version = "0.0.25";
3398 auto constexpr sample_rate = 48000;
3399 auto constexpr frames = 240;
3401 boost::filesystem::remove_all(path);
3402 boost::filesystem::create_directories(path);
3403 auto dcp = make_shared<dcp::DCP>(path);
3404 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3405 cpl->set_annotation_text("hello");
3406 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3407 cpl->set_main_sound_sample_rate(sample_rate);
3408 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3409 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3410 cpl->set_version_number(1);
3414 /* Reel with 2 channels of audio */
3416 auto mp = simple_picture(path, "1", frames, {});
3417 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3419 auto reel = make_shared<dcp::Reel>(
3420 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3421 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3424 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3425 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3432 /* Reel with 6 channels of audio */
3434 auto mp = simple_picture(path, "2", frames, {});
3435 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3437 auto reel = make_shared<dcp::Reel>(
3438 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3439 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3442 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3443 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3450 dcp->set_annotation_text("hello");
3453 check_verify_result(
3456 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3461 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3463 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3465 dcp::MXFMetadata mxf_meta;
3466 mxf_meta.company_name = "OpenDCP";
3467 mxf_meta.product_name = "OpenDCP";
3468 mxf_meta.product_version = "0.0.25";
3470 auto constexpr sample_rate = 48000;
3471 auto constexpr frames = 240;
3473 boost::filesystem::remove_all(path);
3474 boost::filesystem::create_directories(path);
3475 auto dcp = make_shared<dcp::DCP>(path);
3476 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3477 cpl->set_annotation_text("hello");
3478 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3479 cpl->set_main_sound_sample_rate(sample_rate);
3480 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3481 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3482 cpl->set_version_number(1);
3484 auto mp = simple_picture(path, "1", frames, {});
3485 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3487 auto reel = make_shared<dcp::Reel>(
3488 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3489 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3492 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3493 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3494 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3500 dcp->set_annotation_text("hello");
3503 check_verify_result(
3506 { 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)) },
3511 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3513 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3514 auto constexpr video_frames = 24;
3515 auto constexpr sample_rate = 48000;
3517 boost::filesystem::remove_all(path);
3518 boost::filesystem::create_directories(path);
3520 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3521 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3523 dcp::Size const size(1998, 1080);
3524 auto image = make_shared<dcp::OpenJPEGImage>(size);
3525 boost::random::mt19937 rng(1);
3526 boost::random::uniform_int_distribution<> dist(0, 4095);
3527 for (int c = 0; c < 3; ++c) {
3528 for (int p = 0; p < (1998 * 1080); ++p) {
3529 image->data(c)[p] = dist(rng);
3532 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3533 for (int i = 0; i < 24; ++i) {
3534 picture_writer->write(j2c.data(), j2c.size());
3536 picture_writer->finalize();
3538 auto dcp = make_shared<dcp::DCP>(path);
3539 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3540 cpl->set_content_version(
3541 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3543 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3544 cpl->set_main_sound_sample_rate(sample_rate);
3545 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3546 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3547 cpl->set_version_number(1);
3549 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3551 auto reel = make_shared<dcp::Reel>(
3552 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3553 make_shared<dcp::ReelSoundAsset>(ms, 0)
3558 dcp->set_annotation_text("A Test DCP");
3561 check_verify_result(
3564 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3565 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3566 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3567 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3572 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3574 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3575 check_verify_result(
3578 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3579 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3580 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3581 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3582 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3583 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3588 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3590 path const dir("build/test/verify_missing_load_font");
3591 prepare_directory (dir);
3592 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3594 Editor editor(dir / "subs.xml");
3595 editor.delete_first_line_containing("LoadFont");
3597 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3598 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3599 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3601 check_verify_result (
3603 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3604 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3610 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3612 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3613 prepare_directory(dir);
3614 auto dcp = make_simple (dir, 1, 202);
3617 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3618 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3619 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3620 "<ContentTitleText>Content</ContentTitleText>"
3621 "<AnnotationText>Annotation</AnnotationText>"
3622 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3623 "<ReelNumber>1</ReelNumber>"
3624 "<EditRate>24 1</EditRate>"
3625 "<TimeCodeRate>24</TimeCodeRate>"
3626 "<StartTime>00:00:00:00</StartTime>"
3627 "<Language>de-DE</Language>"
3629 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3630 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3631 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3637 dcp::File xml_file(dir / "subs.xml", "w");
3638 BOOST_REQUIRE(xml_file);
3639 xml_file.write(xml.c_str(), xml.size(), 1);
3641 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3642 subs->write(dir / "subs.mxf");
3644 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3645 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3648 check_verify_result (
3651 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3656 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3658 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3659 boost::filesystem::remove_all(dir);
3661 auto dcp1 = make_simple(dir / "1");
3664 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3666 auto dcp2 = make_simple(dir / "2");
3668 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3670 boost::filesystem::remove(dir / "1" / "video.mxf");
3671 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3673 check_verify_result(
3676 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3681 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3683 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3684 boost::filesystem::remove_all(dir);
3686 auto dcp = make_simple(dir);
3687 BOOST_REQUIRE(dcp->cpls().size() == 1);
3688 auto cpl = dcp->cpls()[0];
3689 cpl->set_content_version(dcp::ContentVersion(""));
3692 check_verify_result(
3695 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())