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,
1339 std::vector<dcp::Ruby>()
1345 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1347 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1348 prepare_directory (dir);
1350 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1351 for (int i = 0; i < 2048; ++i) {
1352 add_test_subtitle (asset, i * 24, i * 24 + 20);
1355 asset->set_language (dcp::LanguageTag("de-DE"));
1356 asset->write (dir / "subs.mxf");
1357 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1358 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1360 check_verify_result (
1363 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1365 dcp::VerificationNote::Type::BV21_ERROR,
1366 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1368 canonical(dir / "subs.mxf")
1370 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1371 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1377 shared_ptr<dcp::SMPTESubtitleAsset>
1378 make_large_subtitle_asset (path font_file)
1380 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1381 dcp::ArrayData big_fake_font(1024 * 1024);
1382 big_fake_font.write (font_file);
1383 for (int i = 0; i < 116; ++i) {
1384 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1392 verify_timed_text_asset_too_large (string name)
1394 auto const dir = path("build/test") / name;
1395 prepare_directory (dir);
1396 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1397 add_test_subtitle (asset, 0, 240);
1398 asset->set_language (dcp::LanguageTag("de-DE"));
1399 asset->write (dir / "subs.mxf");
1401 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1402 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1404 check_verify_result (
1407 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1408 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1409 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1410 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1411 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1416 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1418 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1419 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1423 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1425 path dir = "build/test/verify_missing_subtitle_language";
1426 prepare_directory (dir);
1427 auto dcp = make_simple (dir, 1, 106);
1430 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1431 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1432 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1433 "<ContentTitleText>Content</ContentTitleText>"
1434 "<AnnotationText>Annotation</AnnotationText>"
1435 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1436 "<ReelNumber>1</ReelNumber>"
1437 "<EditRate>24 1</EditRate>"
1438 "<TimeCodeRate>24</TimeCodeRate>"
1439 "<StartTime>00:00:00:00</StartTime>"
1440 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1442 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1443 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1444 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1450 dcp::File xml_file(dir / "subs.xml", "w");
1451 BOOST_REQUIRE (xml_file);
1452 xml_file.write(xml.c_str(), xml.size(), 1);
1454 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1455 subs->write (dir / "subs.mxf");
1457 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1458 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1461 check_verify_result (
1464 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1465 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1470 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1472 path path ("build/test/verify_mismatched_subtitle_languages");
1473 auto constexpr reel_length = 192;
1474 auto dcp = make_simple (path, 2, reel_length);
1475 auto cpl = dcp->cpls()[0];
1478 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1479 subs->set_language (dcp::LanguageTag("de-DE"));
1480 subs->add (simple_subtitle());
1482 subs->write (path / "subs1.mxf");
1483 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1484 cpl->reels()[0]->add(reel_subs);
1488 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1489 subs->set_language (dcp::LanguageTag("en-US"));
1490 subs->add (simple_subtitle());
1492 subs->write (path / "subs2.mxf");
1493 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1494 cpl->reels()[1]->add(reel_subs);
1499 check_verify_result (
1502 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1503 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1504 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1509 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1511 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1512 auto constexpr reel_length = 192;
1513 auto dcp = make_simple (path, 2, reel_length);
1514 auto cpl = dcp->cpls()[0];
1517 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1518 ccaps->set_language (dcp::LanguageTag("de-DE"));
1519 ccaps->add (simple_subtitle());
1521 ccaps->write (path / "subs1.mxf");
1522 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1523 cpl->reels()[0]->add(reel_ccaps);
1527 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1528 ccaps->set_language (dcp::LanguageTag("en-US"));
1529 ccaps->add (simple_subtitle());
1531 ccaps->write (path / "subs2.mxf");
1532 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1533 cpl->reels()[1]->add(reel_ccaps);
1538 check_verify_result (
1541 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1542 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1547 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1549 path dir = "build/test/verify_missing_subtitle_start_time";
1550 prepare_directory (dir);
1551 auto dcp = make_simple (dir, 1, 106);
1554 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1555 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1556 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1557 "<ContentTitleText>Content</ContentTitleText>"
1558 "<AnnotationText>Annotation</AnnotationText>"
1559 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1560 "<ReelNumber>1</ReelNumber>"
1561 "<Language>de-DE</Language>"
1562 "<EditRate>24 1</EditRate>"
1563 "<TimeCodeRate>24</TimeCodeRate>"
1564 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1566 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1567 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1568 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1574 dcp::File xml_file(dir / "subs.xml", "w");
1575 BOOST_REQUIRE (xml_file);
1576 xml_file.write(xml.c_str(), xml.size(), 1);
1578 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1579 subs->write (dir / "subs.mxf");
1581 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1582 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1585 check_verify_result (
1588 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1589 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1594 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1596 path dir = "build/test/verify_invalid_subtitle_start_time";
1597 prepare_directory (dir);
1598 auto dcp = make_simple (dir, 1, 106);
1601 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1602 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1603 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1604 "<ContentTitleText>Content</ContentTitleText>"
1605 "<AnnotationText>Annotation</AnnotationText>"
1606 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1607 "<ReelNumber>1</ReelNumber>"
1608 "<Language>de-DE</Language>"
1609 "<EditRate>24 1</EditRate>"
1610 "<TimeCodeRate>24</TimeCodeRate>"
1611 "<StartTime>00:00:02:00</StartTime>"
1612 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1614 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1615 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1616 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1622 dcp::File xml_file(dir / "subs.xml", "w");
1623 BOOST_REQUIRE (xml_file);
1624 xml_file.write(xml.c_str(), xml.size(), 1);
1626 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1627 subs->write (dir / "subs.mxf");
1629 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1630 dcp->cpls().front()->reels().front()->add(reel_subs);
1633 check_verify_result (
1636 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1645 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1648 , v_position(v_position_)
1656 dcp::VAlign v_align;
1662 shared_ptr<dcp::CPL>
1663 dcp_with_text (path dir, vector<TestText> subs)
1665 prepare_directory (dir);
1666 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1667 asset->set_start_time (dcp::Time());
1668 for (auto i: subs) {
1669 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1671 asset->set_language (dcp::LanguageTag("de-DE"));
1673 asset->write (dir / "subs.mxf");
1675 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1676 return write_dcp_with_single_asset (dir, reel_asset);
1681 shared_ptr<dcp::CPL>
1682 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1684 prepare_directory (dir);
1685 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1686 asset->set_start_time (dcp::Time());
1687 asset->set_language (dcp::LanguageTag("de-DE"));
1689 auto subs_mxf = dir / "subs.mxf";
1690 asset->write (subs_mxf);
1692 /* The call to write() puts the asset into the DCP correctly but it will have
1693 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1696 ASDCP::TimedText::MXFWriter writer;
1697 ASDCP::WriterInfo writer_info;
1698 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1700 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1701 DCP_ASSERT (c == Kumu::UUID_Length);
1702 ASDCP::TimedText::TimedTextDescriptor descriptor;
1703 descriptor.ContainerDuration = asset->intrinsic_duration();
1704 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1705 DCP_ASSERT (c == Kumu::UUID_Length);
1706 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1707 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1708 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1709 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1712 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1713 return write_dcp_with_single_asset (dir, reel_asset);
1717 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1719 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1720 /* Just too early */
1721 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1722 check_verify_result (
1725 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1732 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1734 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1735 /* Just late enough */
1736 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1737 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1741 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1743 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1744 prepare_directory (dir);
1746 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1747 asset1->set_start_time (dcp::Time());
1748 /* Just late enough */
1749 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1750 asset1->set_language (dcp::LanguageTag("de-DE"));
1752 asset1->write (dir / "subs1.mxf");
1753 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1754 auto reel1 = make_shared<dcp::Reel>();
1755 reel1->add (reel_asset1);
1756 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1757 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1758 reel1->add (markers1);
1760 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1761 asset2->set_start_time (dcp::Time());
1763 /* This would be too early on first reel but should be OK on the second */
1764 add_test_subtitle (asset2, 3, 4 * 24);
1765 asset2->set_language (dcp::LanguageTag("de-DE"));
1766 asset2->write (dir / "subs2.mxf");
1767 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1768 auto reel2 = make_shared<dcp::Reel>();
1769 reel2->add (reel_asset2);
1770 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1771 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1772 reel2->add (markers2);
1774 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1777 auto dcp = make_shared<dcp::DCP>(dir);
1779 dcp->set_annotation_text("hello");
1782 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1786 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1788 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1789 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1793 { 5 * 24 + 1, 6 * 24 },
1795 check_verify_result (
1798 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1799 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1804 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1806 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1807 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1811 { 5 * 24 + 16, 8 * 24 },
1813 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1817 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1819 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1820 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1821 check_verify_result (
1824 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1825 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1830 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1832 auto const dir = path("build/test/verify_valid_subtitle_duration");
1833 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1834 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1838 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1840 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1841 prepare_directory (dir);
1842 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1843 asset->set_start_time (dcp::Time());
1844 add_test_subtitle (asset, 0, 4 * 24);
1846 asset->set_language (dcp::LanguageTag("de-DE"));
1847 asset->write (dir / "subs.mxf");
1849 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1850 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1851 check_verify_result (
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1855 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1856 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1863 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1865 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1866 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1869 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1870 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1871 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1872 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1874 check_verify_result (
1877 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1878 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1883 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1885 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1886 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1889 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1890 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1891 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1893 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1897 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1899 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1900 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1903 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1904 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1905 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1906 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1908 check_verify_result (
1911 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1917 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1919 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1920 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1923 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1924 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1925 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1926 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1928 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1932 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1934 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1935 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1938 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1940 check_verify_result (
1943 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1944 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1949 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1951 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1952 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1955 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1957 check_verify_result (
1960 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1961 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1966 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1968 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1969 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1972 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1973 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1974 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1975 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1977 check_verify_result (
1980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1986 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1988 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1989 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1992 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1993 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1994 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1996 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2000 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2002 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2003 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2006 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2007 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2008 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2009 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2011 check_verify_result (
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2020 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2022 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2023 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2026 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2027 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2028 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2029 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2031 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2035 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2037 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2038 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2041 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2043 check_verify_result (
2046 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2051 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2053 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2054 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2057 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2059 check_verify_result (
2062 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2063 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2068 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2070 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2071 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2074 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2075 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2076 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2078 check_verify_result (
2081 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2086 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2088 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2089 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2092 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2093 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2094 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2096 check_verify_result (
2099 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2100 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2105 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2107 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2108 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2111 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2112 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2113 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2115 check_verify_result (
2118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2123 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2125 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2126 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2129 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2130 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2131 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2133 check_verify_result (
2136 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2141 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2143 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2144 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2145 check_verify_result (
2148 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2149 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2154 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2156 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2157 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2158 check_verify_result (
2161 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2167 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2169 path const dir("build/test/verify_invalid_sound_frame_rate");
2170 prepare_directory (dir);
2172 auto picture = simple_picture (dir, "foo");
2173 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2174 auto reel = make_shared<dcp::Reel>();
2175 reel->add (reel_picture);
2176 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2177 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2178 reel->add (reel_sound);
2179 reel->add (simple_markers());
2180 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2182 auto dcp = make_shared<dcp::DCP>(dir);
2184 dcp->set_annotation_text("hello");
2187 check_verify_result (
2190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2191 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2196 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2198 path const dir("build/test/verify_missing_cpl_annotation_text");
2199 auto dcp = make_simple (dir);
2202 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2204 auto const cpl = dcp->cpls()[0];
2207 BOOST_REQUIRE (cpl->file());
2208 Editor e(cpl->file().get());
2209 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2216 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2221 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2223 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2224 auto dcp = make_simple (dir);
2227 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2228 auto const cpl = dcp->cpls()[0];
2231 BOOST_REQUIRE (cpl->file());
2232 Editor e(cpl->file().get());
2233 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2236 check_verify_result (
2239 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2240 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2245 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2247 path const dir("build/test/verify_mismatched_asset_duration");
2248 prepare_directory (dir);
2249 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2250 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2252 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2253 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2255 auto reel = make_shared<dcp::Reel>(
2256 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2257 make_shared<dcp::ReelSoundAsset>(ms, 0)
2260 reel->add (simple_markers());
2264 dcp->set_annotation_text("A Test DCP");
2267 check_verify_result (
2270 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2271 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2278 shared_ptr<dcp::CPL>
2279 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2281 prepare_directory (dir);
2282 auto dcp = make_shared<dcp::DCP>(dir);
2283 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2285 auto constexpr reel_length = 192;
2287 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2288 subs->set_language (dcp::LanguageTag("de-DE"));
2289 subs->set_start_time (dcp::Time());
2290 subs->add (simple_subtitle());
2292 subs->write (dir / "subs.mxf");
2293 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2295 auto reel1 = make_shared<dcp::Reel>(
2296 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2297 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2301 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2304 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2305 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2306 reel1->add (markers1);
2310 auto reel2 = make_shared<dcp::Reel>(
2311 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2312 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2316 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2319 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2320 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2321 reel2->add (markers2);
2326 dcp->set_annotation_text("A Test DCP");
2333 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2336 path dir ("build/test/missing_main_subtitle_from_some_reels");
2337 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2338 check_verify_result (
2341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2342 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2348 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2349 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2350 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2354 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2355 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2356 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2362 shared_ptr<dcp::CPL>
2363 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2365 prepare_directory (dir);
2366 auto dcp = make_shared<dcp::DCP>(dir);
2367 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2369 auto constexpr reel_length = 192;
2371 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2372 subs->set_language (dcp::LanguageTag("de-DE"));
2373 subs->set_start_time (dcp::Time());
2374 subs->add (simple_subtitle());
2376 subs->write (dir / "subs.mxf");
2378 auto reel1 = make_shared<dcp::Reel>(
2379 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "1", reel_length), 0),
2380 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "1", dcp::MXFMetadata(), "en-US", reel_length), 0)
2383 for (int i = 0; i < caps_in_reel1; ++i) {
2384 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2387 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2388 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2389 reel1->add (markers1);
2393 auto reel2 = make_shared<dcp::Reel>(
2394 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "2", reel_length), 0),
2395 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "2", dcp::MXFMetadata(), "en-US", reel_length), 0)
2398 for (int i = 0; i < caps_in_reel2; ++i) {
2399 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2402 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2403 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2404 reel2->add (markers2);
2409 dcp->set_annotation_text("A Test DCP");
2416 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2419 path dir ("build/test/mismatched_closed_caption_asset_counts");
2420 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2421 check_verify_result (
2424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2425 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2430 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2431 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2432 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2436 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2437 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2438 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2445 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2447 prepare_directory (dir);
2448 auto dcp = make_shared<dcp::DCP>(dir);
2449 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2451 auto constexpr reel_length = 192;
2453 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2454 subs->set_language (dcp::LanguageTag("de-DE"));
2455 subs->set_start_time (dcp::Time());
2456 subs->add (simple_subtitle());
2458 subs->write (dir / "subs.mxf");
2459 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2462 auto reel = make_shared<dcp::Reel>(
2463 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2464 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2467 reel->add (reel_text);
2469 reel->add (simple_markers(reel_length));
2474 dcp->set_annotation_text("A Test DCP");
2477 check_verify_result (
2480 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2486 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2488 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2489 "build/test/verify_subtitle_entry_point_must_be_present",
2490 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2491 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2492 asset->unset_entry_point ();
2496 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2497 "build/test/verify_subtitle_entry_point_must_be_zero",
2498 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2499 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2500 asset->set_entry_point (4);
2504 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2505 "build/test/verify_closed_caption_entry_point_must_be_present",
2506 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2507 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2508 asset->unset_entry_point ();
2512 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2513 "build/test/verify_closed_caption_entry_point_must_be_zero",
2514 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2515 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2516 asset->set_entry_point (9);
2522 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2526 path const dir("build/test/verify_missing_hash");
2527 auto dcp = make_simple (dir);
2530 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2531 auto const cpl = dcp->cpls()[0];
2532 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2533 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2534 auto asset_id = cpl->reels()[0]->main_picture()->id();
2537 BOOST_REQUIRE (cpl->file());
2538 Editor e(cpl->file().get());
2539 e.delete_first_line_containing("<Hash>");
2542 check_verify_result (
2545 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2546 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2553 verify_markers_test (
2555 vector<pair<dcp::Marker, dcp::Time>> markers,
2556 vector<dcp::VerificationNote> test_notes
2559 auto dcp = make_simple (dir);
2560 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2561 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2562 for (auto const& i: markers) {
2563 markers_asset->set (i.first, i.second);
2565 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2568 check_verify_result ({dir}, test_notes);
2572 BOOST_AUTO_TEST_CASE (verify_markers)
2574 verify_markers_test (
2575 "build/test/verify_markers_all_correct",
2577 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2578 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2579 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2580 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2585 verify_markers_test (
2586 "build/test/verify_markers_missing_ffec",
2588 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2589 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2590 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2593 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2596 verify_markers_test (
2597 "build/test/verify_markers_missing_ffmc",
2599 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2600 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2601 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2607 verify_markers_test (
2608 "build/test/verify_markers_missing_ffoc",
2610 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2611 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2612 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2615 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2618 verify_markers_test (
2619 "build/test/verify_markers_missing_lfoc",
2621 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2622 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2623 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2626 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2629 verify_markers_test (
2630 "build/test/verify_markers_incorrect_ffoc",
2632 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2633 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2634 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2635 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2638 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2641 verify_markers_test (
2642 "build/test/verify_markers_incorrect_lfoc",
2644 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2645 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2646 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2647 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2650 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2655 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2657 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2658 prepare_directory (dir);
2659 auto dcp = make_simple (dir);
2660 auto cpl = dcp->cpls()[0];
2661 cpl->unset_version_number();
2664 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2668 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2670 path dir = "build/test/verify_missing_extension_metadata1";
2671 auto dcp = make_simple (dir);
2674 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2675 auto cpl = dcp->cpls()[0];
2678 Editor e (cpl->file().get());
2679 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2682 check_verify_result (
2685 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2686 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2691 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2693 path dir = "build/test/verify_missing_extension_metadata2";
2694 auto dcp = make_simple (dir);
2697 auto cpl = dcp->cpls()[0];
2700 Editor e (cpl->file().get());
2701 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2704 check_verify_result (
2707 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2708 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2713 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2715 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2716 auto dcp = make_simple (dir);
2719 auto const cpl = dcp->cpls()[0];
2722 Editor e (cpl->file().get());
2723 e.replace ("<meta:Name>A", "<meta:NameX>A");
2724 e.replace ("n</meta:Name>", "n</meta:NameX>");
2727 check_verify_result (
2730 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2731 { 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 },
2732 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2737 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2739 path dir = "build/test/verify_invalid_extension_metadata1";
2740 auto dcp = make_simple (dir);
2743 auto cpl = dcp->cpls()[0];
2746 Editor e (cpl->file().get());
2747 e.replace ("Application", "Fred");
2750 check_verify_result (
2753 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2754 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2759 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2761 path dir = "build/test/verify_invalid_extension_metadata2";
2762 auto dcp = make_simple (dir);
2765 auto cpl = dcp->cpls()[0];
2768 Editor e (cpl->file().get());
2769 e.replace ("DCP Constraints Profile", "Fred");
2772 check_verify_result (
2775 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2781 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2783 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2784 auto dcp = make_simple (dir);
2787 auto const cpl = dcp->cpls()[0];
2790 Editor e (cpl->file().get());
2791 e.replace ("<meta:Value>", "<meta:ValueX>");
2792 e.replace ("</meta:Value>", "</meta:ValueX>");
2795 check_verify_result (
2798 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2799 { 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 },
2800 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2805 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2807 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2808 auto dcp = make_simple (dir);
2811 auto const cpl = dcp->cpls()[0];
2814 Editor e (cpl->file().get());
2815 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2818 check_verify_result (
2821 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2822 { 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() },
2827 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2829 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2830 auto dcp = make_simple (dir);
2833 auto const cpl = dcp->cpls()[0];
2836 Editor e (cpl->file().get());
2837 e.replace ("<meta:Property>", "<meta:PropertyX>");
2838 e.replace ("</meta:Property>", "</meta:PropertyX>");
2841 check_verify_result (
2844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2846 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2851 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2853 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2854 auto dcp = make_simple (dir);
2857 auto const cpl = dcp->cpls()[0];
2860 Editor e (cpl->file().get());
2861 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2862 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2865 check_verify_result (
2868 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2869 { 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 },
2870 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2876 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2878 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2879 prepare_directory (dir);
2880 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2881 copy_file (i.path(), dir / i.path().filename());
2884 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id() + ".xml" );
2885 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id() + ".xml");
2889 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2892 check_verify_result (
2895 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id(), canonical(cpl) },
2896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl), },
2897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2898 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2899 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2900 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id(), canonical(cpl) }
2907 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2909 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2910 prepare_directory (dir);
2911 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2912 copy_file (i.path(), dir / i.path().filename());
2915 path const cpl = dir / ("cpl_" + encryption_test_cpl_id() + ".xml");
2916 path const pkl = dir / ("pkl_" + encryption_test_pkl_id() + ".xml");
2919 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2922 check_verify_result (
2925 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id(), canonical(pkl) },
2926 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2928 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2929 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id(), canonical(cpl) },
2931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id(), canonical(pkl) },
2936 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2938 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2939 prepare_directory (dir);
2940 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2941 copy_file (i.path(), dir / i.path().filename());
2945 Editor e (dir / dcp_test1_pkl());
2946 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2949 check_verify_result ({dir}, {});
2953 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2955 path dir ("build/test/verify_must_not_be_partially_encrypted");
2956 prepare_directory (dir);
2960 auto signer = make_shared<dcp::CertificateChain>();
2961 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2962 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2963 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2964 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2966 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2970 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2973 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
2974 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2975 for (int i = 0; i < 24; ++i) {
2976 writer->write (j2c.data(), j2c.size());
2978 writer->finalize ();
2980 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2982 auto reel = make_shared<dcp::Reel>(
2983 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2984 make_shared<dcp::ReelSoundAsset>(ms, 0)
2987 reel->add (simple_markers());
2991 cpl->set_content_version (
2992 {"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"}
2994 cpl->set_annotation_text ("A Test DCP");
2995 cpl->set_issuer ("OpenDCP 0.0.25");
2996 cpl->set_creator ("OpenDCP 0.0.25");
2997 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2998 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
2999 cpl->set_main_sound_sample_rate (48000);
3000 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3001 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3002 cpl->set_version_number (1);
3006 d.set_issuer("OpenDCP 0.0.25");
3007 d.set_creator("OpenDCP 0.0.25");
3008 d.set_issue_date("2012-07-17T04:45:18+00:00");
3009 d.set_annotation_text("A Test DCP");
3010 d.write_xml(signer);
3012 check_verify_result (
3015 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3020 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3022 vector<dcp::VerificationNote> notes;
3023 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"));
3024 auto reader = picture.start_read ();
3025 auto frame = reader->get_frame (0);
3026 verify_j2k(frame, 0, 24, notes);
3027 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3031 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3033 vector<dcp::VerificationNote> notes;
3034 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3035 auto reader = picture.start_read ();
3036 auto frame = reader->get_frame (0);
3037 verify_j2k(frame, 0, 24, notes);
3038 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3042 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3044 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3045 prepare_directory (dir);
3046 auto dcp = make_simple (dir);
3048 vector<dcp::VerificationNote> notes;
3049 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3050 auto reader = picture.start_read ();
3051 auto frame = reader->get_frame (0);
3052 verify_j2k(frame, 0, 24, notes);
3053 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3057 /** Check that ResourceID and the XML ID being different is spotted */
3058 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3060 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3061 prepare_directory (dir);
3063 ASDCP::WriterInfo writer_info;
3064 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3067 auto mxf_id = dcp::make_uuid ();
3068 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3069 BOOST_REQUIRE (c == Kumu::UUID_Length);
3071 auto resource_id = dcp::make_uuid ();
3072 ASDCP::TimedText::TimedTextDescriptor descriptor;
3073 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3074 DCP_ASSERT (c == Kumu::UUID_Length);
3076 auto xml_id = dcp::make_uuid ();
3077 ASDCP::TimedText::MXFWriter writer;
3078 auto subs_mxf = dir / "subs.mxf";
3079 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3080 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3081 writer.WriteTimedTextResource (dcp::String::compose(
3082 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3083 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3084 "<Id>urn:uuid:%1</Id>"
3085 "<ContentTitleText>Content</ContentTitleText>"
3086 "<AnnotationText>Annotation</AnnotationText>"
3087 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3088 "<ReelNumber>1</ReelNumber>"
3089 "<Language>en-US</Language>"
3090 "<EditRate>25 1</EditRate>"
3091 "<TimeCodeRate>25</TimeCodeRate>"
3092 "<StartTime>00:00:00:00</StartTime>"
3093 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3095 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3096 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3097 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3106 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3107 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3109 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3111 check_verify_result (
3114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3115 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3116 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3122 /** Check that ResourceID and the MXF ID being the same is spotted */
3123 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3125 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3126 prepare_directory (dir);
3128 ASDCP::WriterInfo writer_info;
3129 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3132 auto mxf_id = dcp::make_uuid ();
3133 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3134 BOOST_REQUIRE (c == Kumu::UUID_Length);
3136 auto resource_id = mxf_id;
3137 ASDCP::TimedText::TimedTextDescriptor descriptor;
3138 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3139 DCP_ASSERT (c == Kumu::UUID_Length);
3141 auto xml_id = resource_id;
3142 ASDCP::TimedText::MXFWriter writer;
3143 auto subs_mxf = dir / "subs.mxf";
3144 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3145 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3146 writer.WriteTimedTextResource (dcp::String::compose(
3147 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3148 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3149 "<Id>urn:uuid:%1</Id>"
3150 "<ContentTitleText>Content</ContentTitleText>"
3151 "<AnnotationText>Annotation</AnnotationText>"
3152 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3153 "<ReelNumber>1</ReelNumber>"
3154 "<Language>en-US</Language>"
3155 "<EditRate>25 1</EditRate>"
3156 "<TimeCodeRate>25</TimeCodeRate>"
3157 "<StartTime>00:00:00:00</StartTime>"
3158 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3160 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3161 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3162 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3171 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3172 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3174 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3176 check_verify_result (
3179 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3180 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3181 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3182 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3183 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3188 /** Check a DCP with a 3D asset marked as 2D */
3189 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3191 check_verify_result (
3192 { private_test / "data" / "xm" },
3195 dcp::VerificationNote::Type::WARNING,
3196 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3199 dcp::VerificationNote::Type::BV21_ERROR,
3200 dcp::VerificationNote::Code::INVALID_STANDARD
3207 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3209 path dir = "build/test/verify_unexpected_things_in_main_markers";
3210 prepare_directory (dir);
3211 auto dcp = make_simple (dir, 1, 24);
3215 Editor e (find_cpl(dir));
3217 " <IntrinsicDuration>24</IntrinsicDuration>",
3218 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3222 dcp::CPL cpl (find_cpl(dir));
3224 check_verify_result (
3227 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3228 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3229 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3234 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3236 path dir = "build/test/verify_invalid_content_kind";
3237 prepare_directory (dir);
3238 auto dcp = make_simple (dir, 1, 24);
3242 Editor e(find_cpl(dir));
3243 e.replace("trailer", "trip");
3246 dcp::CPL cpl (find_cpl(dir));
3248 check_verify_result (
3251 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3252 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3258 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3260 path dir = "build/test/verify_valid_content_kind";
3261 prepare_directory (dir);
3262 auto dcp = make_simple (dir, 1, 24);
3266 Editor e(find_cpl(dir));
3267 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3270 dcp::CPL cpl (find_cpl(dir));
3272 check_verify_result (
3275 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3281 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3283 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3284 prepare_directory(dir);
3285 auto dcp = make_simple(dir, 1, 24);
3288 auto constexpr area = "<meta:MainPictureActiveArea>";
3291 Editor e(find_cpl(dir));
3292 e.delete_lines_after(area, 2);
3293 e.insert(area, "<meta:Height>4080</meta:Height>");
3294 e.insert(area, "<meta:Width>1997</meta:Width>");
3297 dcp::PKL pkl(find_pkl(dir));
3298 dcp::CPL cpl(find_cpl(dir));
3300 check_verify_result(
3303 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3304 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3305 { 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)) },
3310 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3312 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3313 prepare_directory(dir);
3314 auto dcp = make_simple(dir, 1, 24);
3317 auto constexpr area = "<meta:MainPictureActiveArea>";
3320 Editor e(find_cpl(dir));
3321 e.delete_lines_after(area, 2);
3322 e.insert(area, "<meta:Height>5125</meta:Height>");
3323 e.insert(area, "<meta:Width>9900</meta:Width>");
3326 dcp::PKL pkl(find_pkl(dir));
3327 dcp::CPL cpl(find_cpl(dir));
3329 check_verify_result(
3332 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3333 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3334 { 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)) },
3335 { 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)) },
3340 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3344 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3345 prepare_directory(dir);
3346 auto dcp = make_simple(dir, 1, 24);
3350 Editor e(find_pkl(dir));
3351 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3354 dcp::PKL pkl(find_pkl(dir));
3356 check_verify_result(
3359 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3364 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3368 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3369 prepare_directory(dir);
3370 auto dcp = make_simple(dir, 1, 24);
3374 Editor e(find_asset_map(dir));
3375 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3378 dcp::PKL pkl(find_pkl(dir));
3379 dcp::AssetMap asset_map(find_asset_map(dir));
3381 check_verify_result(
3384 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3385 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3390 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3392 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3394 dcp::MXFMetadata mxf_meta;
3395 mxf_meta.company_name = "OpenDCP";
3396 mxf_meta.product_name = "OpenDCP";
3397 mxf_meta.product_version = "0.0.25";
3399 auto constexpr sample_rate = 48000;
3400 auto constexpr frames = 240;
3402 boost::filesystem::remove_all(path);
3403 boost::filesystem::create_directories(path);
3404 auto dcp = make_shared<dcp::DCP>(path);
3405 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3406 cpl->set_annotation_text("hello");
3407 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3408 cpl->set_main_sound_sample_rate(sample_rate);
3409 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3410 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3411 cpl->set_version_number(1);
3415 /* Reel with 2 channels of audio */
3417 auto mp = simple_picture(path, "1", frames, {});
3418 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3420 auto reel = make_shared<dcp::Reel>(
3421 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3422 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3425 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3426 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3433 /* Reel with 6 channels of audio */
3435 auto mp = simple_picture(path, "2", frames, {});
3436 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3438 auto reel = make_shared<dcp::Reel>(
3439 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3440 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3443 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3444 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3451 dcp->set_annotation_text("hello");
3454 check_verify_result(
3457 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3462 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3464 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3466 dcp::MXFMetadata mxf_meta;
3467 mxf_meta.company_name = "OpenDCP";
3468 mxf_meta.product_name = "OpenDCP";
3469 mxf_meta.product_version = "0.0.25";
3471 auto constexpr sample_rate = 48000;
3472 auto constexpr frames = 240;
3474 boost::filesystem::remove_all(path);
3475 boost::filesystem::create_directories(path);
3476 auto dcp = make_shared<dcp::DCP>(path);
3477 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3478 cpl->set_annotation_text("hello");
3479 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3480 cpl->set_main_sound_sample_rate(sample_rate);
3481 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3482 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3483 cpl->set_version_number(1);
3485 auto mp = simple_picture(path, "1", frames, {});
3486 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3488 auto reel = make_shared<dcp::Reel>(
3489 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3490 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3493 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3494 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3495 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3501 dcp->set_annotation_text("hello");
3504 check_verify_result(
3507 { 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)) },
3512 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3514 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3515 auto constexpr video_frames = 24;
3516 auto constexpr sample_rate = 48000;
3518 boost::filesystem::remove_all(path);
3519 boost::filesystem::create_directories(path);
3521 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3522 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3524 dcp::Size const size(1998, 1080);
3525 auto image = make_shared<dcp::OpenJPEGImage>(size);
3526 boost::random::mt19937 rng(1);
3527 boost::random::uniform_int_distribution<> dist(0, 4095);
3528 for (int c = 0; c < 3; ++c) {
3529 for (int p = 0; p < (1998 * 1080); ++p) {
3530 image->data(c)[p] = dist(rng);
3533 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3534 for (int i = 0; i < 24; ++i) {
3535 picture_writer->write(j2c.data(), j2c.size());
3537 picture_writer->finalize();
3539 auto dcp = make_shared<dcp::DCP>(path);
3540 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3541 cpl->set_content_version(
3542 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3544 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3545 cpl->set_main_sound_sample_rate(sample_rate);
3546 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3547 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3548 cpl->set_version_number(1);
3550 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3552 auto reel = make_shared<dcp::Reel>(
3553 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3554 make_shared<dcp::ReelSoundAsset>(ms, 0)
3559 dcp->set_annotation_text("A Test DCP");
3562 check_verify_result(
3565 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3566 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3567 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3568 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3573 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3575 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3576 check_verify_result(
3579 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3580 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3581 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3582 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3583 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3584 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3589 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3591 path const dir("build/test/verify_missing_load_font");
3592 prepare_directory (dir);
3593 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3595 Editor editor(dir / "subs.xml");
3596 editor.delete_first_line_containing("LoadFont");
3598 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3599 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3600 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3602 check_verify_result (
3604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3605 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3611 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3613 boost::filesystem::path const dir = "build/test/verify_missing_load_font";
3614 prepare_directory(dir);
3615 auto dcp = make_simple (dir, 1, 202);
3618 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3619 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3620 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3621 "<ContentTitleText>Content</ContentTitleText>"
3622 "<AnnotationText>Annotation</AnnotationText>"
3623 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3624 "<ReelNumber>1</ReelNumber>"
3625 "<EditRate>24 1</EditRate>"
3626 "<TimeCodeRate>24</TimeCodeRate>"
3627 "<StartTime>00:00:00:00</StartTime>"
3628 "<Language>de-DE</Language>"
3630 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3631 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3632 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3638 dcp::File xml_file(dir / "subs.xml", "w");
3639 BOOST_REQUIRE(xml_file);
3640 xml_file.write(xml.c_str(), xml.size(), 1);
3642 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3643 subs->write(dir / "subs.mxf");
3645 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3646 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3649 check_verify_result (
3652 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())
3657 BOOST_AUTO_TEST_CASE(verify_spots_wrong_asset)
3659 boost::filesystem::path const dir = "build/test/verify_spots_wrong_asset";
3660 boost::filesystem::remove_all(dir);
3662 auto dcp1 = make_simple(dir / "1");
3665 auto const asset_1 = dcp::MonoPictureAsset(dir / "1" / "video.mxf").id();
3667 auto dcp2 = make_simple(dir / "2");
3669 auto const asset_2 = dcp::MonoPictureAsset(dir / "2" / "video.mxf").id();
3671 boost::filesystem::remove(dir / "1" / "video.mxf");
3672 boost::filesystem::copy_file(dir / "2" / "video.mxf", dir / "1" / "video.mxf");
3674 check_verify_result(
3677 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_MAP_ID).set_id(asset_1).set_other_id(asset_2)
3682 BOOST_AUTO_TEST_CASE(verify_cpl_content_version_label_text_empty)
3684 boost::filesystem::path const dir = "build/test/verify_cpl_content_version_label_text_empty";
3685 boost::filesystem::remove_all(dir);
3687 auto dcp = make_simple(dir);
3688 BOOST_REQUIRE(dcp->cpls().size() == 1);
3689 auto cpl = dcp->cpls()[0];
3690 cpl->set_content_version(dcp::ContentVersion(""));
3693 check_verify_result(
3696 dcp::VerificationNote(dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT, cpl->file().get()).set_id(cpl->id())