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/test/unit_test.hpp>
62 #include <boost/algorithm/string.hpp>
69 using std::make_shared;
71 using std::shared_ptr;
74 using boost::optional;
75 using namespace boost::filesystem;
78 static list<pair<string, optional<path>>> stages;
80 static string filename_to_id(boost::filesystem::path path)
82 return path.string().substr(4, path.string().length() - 8);
85 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
86 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
88 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
89 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
91 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
93 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
94 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
96 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
97 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
100 stage (string s, optional<path> p)
102 stages.push_back (make_pair (s, p));
112 prepare_directory (path path)
114 using namespace boost::filesystem;
116 create_directories (path);
120 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
121 * to make a new sacrificial test DCP.
124 setup (int reference_number, string verify_test_suffix)
126 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
127 prepare_directory (dir);
128 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
129 copy_file (i.path(), dir / i.path().filename());
138 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
140 auto reel = make_shared<dcp::Reel>();
141 reel->add (reel_asset);
142 reel->add (simple_markers());
144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
146 auto dcp = make_shared<dcp::DCP>(dir);
148 dcp->set_annotation_text("hello");
155 /** Class that can alter a file by searching and replacing strings within it.
156 * On destruction modifies the file whose name was given to the constructor.
164 _content = dcp::file_to_string (_path);
169 auto f = fopen(_path.string().c_str(), "w");
171 fwrite (_content.c_str(), _content.length(), 1, f);
178 ChangeChecker(Editor* editor)
181 _old_content = _editor->_content;
186 BOOST_REQUIRE(_old_content != _editor->_content);
190 std::string _old_content;
193 void replace (string a, string b)
195 ChangeChecker cc(this);
196 boost::algorithm::replace_all (_content, a, b);
199 void delete_first_line_containing (string s)
201 ChangeChecker cc(this);
202 auto lines = as_lines();
205 for (auto i: lines) {
206 if (i.find(s) == string::npos || done) {
207 _content += i + "\n";
214 void delete_lines (string from, string to)
216 ChangeChecker cc(this);
217 auto lines = as_lines();
218 bool deleting = false;
220 for (auto i: lines) {
221 if (i.find(from) != string::npos) {
225 _content += i + "\n";
227 if (deleting && i.find(to) != string::npos) {
233 void insert (string after, string line)
235 ChangeChecker cc(this);
236 auto lines = as_lines();
238 bool replaced = false;
239 for (auto i: lines) {
240 _content += i + "\n";
241 if (!replaced && i.find(after) != string::npos) {
242 _content += line + "\n";
248 void delete_lines_after(string after, int lines_to_delete)
250 ChangeChecker cc(this);
251 auto lines = as_lines();
253 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
254 return line.find(after) != string::npos;
257 for (auto i = lines.begin(); i != lines.end(); ++i) {
259 to_delete = lines_to_delete;
260 _content += *i + "\n";
261 } else if (to_delete == 0) {
262 _content += *i + "\n";
270 friend class ChangeChecker;
272 vector<string> as_lines() const
274 vector<string> lines;
275 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
280 std::string _content;
284 LIBDCP_DISABLE_WARNINGS
287 dump_notes (vector<dcp::VerificationNote> const & notes)
289 for (auto i: notes) {
290 std::cout << dcp::note_to_string(i) << "\n";
293 LIBDCP_ENABLE_WARNINGS
298 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
300 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
301 std::sort (notes.begin(), notes.end());
302 std::sort (test_notes.begin(), test_notes.end());
304 string message = "\nVerification notes from test:\n";
305 for (auto i: notes) {
306 message += " " + note_to_string(i) + "\n";
307 message += dcp::String::compose(
308 " [%1 %2 %3 %4 %5]\n",
309 static_cast<int>(i.type()),
310 static_cast<int>(i.code()),
311 i.note().get_value_or("<none>"),
312 i.file().get_value_or("<none>"),
313 i.line().get_value_or(0)
316 message += "Expected:\n";
317 for (auto i: test_notes) {
318 message += " " + note_to_string(i) + "\n";
319 message += dcp::String::compose(
320 " [%1 %2 %3 %4 %5]\n",
321 static_cast<int>(i.type()),
322 static_cast<int>(i.code()),
323 i.note().get_value_or("<none>"),
324 i.file().get_value_or("<none>"),
325 i.line().get_value_or(0)
329 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
333 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
334 * replacing from with to. Verify the resulting DCP and check that the results match the given
339 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
341 auto dir = setup (1, suffix);
344 Editor e (file(suffix));
345 e.replace (from, to);
348 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
350 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
351 auto i = notes.begin();
352 auto j = codes.begin();
353 while (i != notes.end()) {
354 BOOST_CHECK_EQUAL (i->code(), *j);
361 BOOST_AUTO_TEST_CASE (verify_no_error)
364 auto dir = setup (1, "no_error");
365 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
367 path const cpl_file = dir / dcp_test1_cpl;
368 path const pkl_file = dir / dcp_test1_pkl;
369 path const assetmap_file = dir / "ASSETMAP.xml";
371 auto st = stages.begin();
372 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
373 BOOST_REQUIRE (st->second);
374 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
376 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
377 BOOST_REQUIRE (st->second);
378 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
380 BOOST_CHECK_EQUAL (st->first, "Checking reel");
381 BOOST_REQUIRE (!st->second);
383 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
384 BOOST_REQUIRE (st->second);
385 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
387 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
388 BOOST_REQUIRE (st->second);
389 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
391 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
392 BOOST_REQUIRE (st->second);
393 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
395 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
396 BOOST_REQUIRE (st->second);
397 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
399 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
400 BOOST_REQUIRE (st->second);
401 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
403 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
404 BOOST_REQUIRE (st->second);
405 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
407 BOOST_REQUIRE (st == stages.end());
409 BOOST_CHECK_EQUAL (notes.size(), 0U);
413 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
415 using namespace boost::filesystem;
417 auto dir = setup (1, "incorrect_picture_sound_hash");
419 auto video_path = path(dir / "video.mxf");
420 auto mod = fopen(video_path.string().c_str(), "r+b");
422 fseek (mod, 4096, SEEK_SET);
424 fwrite (&x, sizeof(x), 1, mod);
427 auto audio_path = path(dir / "audio.mxf");
428 mod = fopen(audio_path.string().c_str(), "r+b");
430 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
431 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
434 dcp::ASDCPErrorSuspender sus;
435 check_verify_result (
438 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
439 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
444 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
446 using namespace boost::filesystem;
448 auto dir = setup (1, "mismatched_picture_sound_hashes");
451 Editor e (dir / dcp_test1_pkl);
452 e.replace ("<Hash>", "<Hash>x");
455 check_verify_result (
458 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
459 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
460 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
461 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 },
462 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
463 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 },
468 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
470 auto dir = setup (1, "failed_read_content_kind");
473 Editor e (dir / dcp_test1_cpl);
474 e.replace ("<ContentKind>", "<ContentKind>x");
477 check_verify_result (
480 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
481 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
490 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
498 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
504 asset_map (string suffix)
506 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
510 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
512 check_verify_result_after_replace (
513 "invalid_picture_frame_rate", &cpl,
514 "<FrameRate>24 1", "<FrameRate>99 1",
515 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
516 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
520 BOOST_AUTO_TEST_CASE (verify_missing_asset)
522 auto dir = setup (1, "missing_asset");
523 remove (dir / "video.mxf");
524 check_verify_result (
527 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
532 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
534 check_verify_result_after_replace (
535 "empty_asset_path", &asset_map,
536 "<Path>video.mxf</Path>", "<Path></Path>",
537 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
542 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
544 check_verify_result_after_replace (
545 "mismatched_standard", &cpl,
546 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
547 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
548 dcp::VerificationNote::Code::INVALID_XML,
549 dcp::VerificationNote::Code::INVALID_XML,
550 dcp::VerificationNote::Code::INVALID_XML,
551 dcp::VerificationNote::Code::INVALID_XML,
552 dcp::VerificationNote::Code::INVALID_XML,
553 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
558 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
560 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
561 check_verify_result_after_replace (
562 "invalid_xml_cpl_id", &cpl,
563 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
564 { dcp::VerificationNote::Code::INVALID_XML }
569 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
571 check_verify_result_after_replace (
572 "invalid_xml_issue_date", &cpl,
573 "<IssueDate>", "<IssueDate>x",
574 { dcp::VerificationNote::Code::INVALID_XML,
575 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
580 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
582 check_verify_result_after_replace (
583 "invalid_xml_pkl_id", &pkl,
584 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
585 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
586 { dcp::VerificationNote::Code::INVALID_XML }
591 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
593 check_verify_result_after_replace (
594 "invalid_xml_asset_map_id", &asset_map,
595 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
596 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
597 { dcp::VerificationNote::Code::INVALID_XML }
602 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
605 auto dir = setup (3, "verify_invalid_standard");
606 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
608 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
609 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
610 path const assetmap_file = dir / "ASSETMAP";
612 auto st = stages.begin();
613 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
614 BOOST_REQUIRE (st->second);
615 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
617 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
618 BOOST_REQUIRE (st->second);
619 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
621 BOOST_CHECK_EQUAL (st->first, "Checking reel");
622 BOOST_REQUIRE (!st->second);
624 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
625 BOOST_REQUIRE (st->second);
626 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
628 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
629 BOOST_REQUIRE (st->second);
630 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
632 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
633 BOOST_REQUIRE (st->second);
634 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
636 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
637 BOOST_REQUIRE (st->second);
638 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
640 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
641 BOOST_REQUIRE (st->second);
642 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
644 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
645 BOOST_REQUIRE (st->second);
646 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
648 BOOST_REQUIRE (st == stages.end());
650 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
651 auto i = notes.begin ();
652 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
653 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
655 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
656 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
659 /* DCP with a short asset */
660 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
662 auto dir = setup (8, "invalid_duration");
663 check_verify_result (
666 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
667 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
670 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
678 dcp_from_frame (dcp::ArrayData const& frame, path dir)
680 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
681 create_directories (dir);
682 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
683 for (int i = 0; i < 24; ++i) {
684 writer->write (frame.data(), frame.size());
688 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
689 return write_dcp_with_single_asset (dir, reel_asset);
693 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
695 int const too_big = 1302083 * 2;
697 /* Compress a black image */
698 auto image = black_image ();
699 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
700 BOOST_REQUIRE (frame.size() < too_big);
702 /* Place it in a bigger block with some zero padding at the end */
703 dcp::ArrayData oversized_frame(too_big);
704 memcpy (oversized_frame.data(), frame.data(), frame.size());
705 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
707 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
708 prepare_directory (dir);
709 auto cpl = dcp_from_frame (oversized_frame, dir);
711 check_verify_result (
714 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
721 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
723 int const nearly_too_big = 1302083 * 0.98;
725 /* Compress a black image */
726 auto image = black_image ();
727 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
728 BOOST_REQUIRE (frame.size() < nearly_too_big);
730 /* Place it in a bigger block with some zero padding at the end */
731 dcp::ArrayData oversized_frame(nearly_too_big);
732 memcpy (oversized_frame.data(), frame.data(), frame.size());
733 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
735 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
736 prepare_directory (dir);
737 auto cpl = dcp_from_frame (oversized_frame, dir);
739 check_verify_result (
742 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
743 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
744 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
749 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
751 /* Compress a black image */
752 auto image = black_image ();
753 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
754 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
756 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
757 prepare_directory (dir);
758 auto cpl = dcp_from_frame (frame, dir);
760 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
764 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
766 path const dir("build/test/verify_valid_interop_subtitles");
767 prepare_directory (dir);
768 copy_file ("test/data/subs1.xml", dir / "subs.xml");
769 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
770 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
771 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
773 check_verify_result (
775 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
776 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
781 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
783 using namespace boost::filesystem;
785 path const dir("build/test/verify_invalid_interop_subtitles");
786 prepare_directory (dir);
787 copy_file ("test/data/subs1.xml", dir / "subs.xml");
788 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
789 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
790 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
793 Editor e (dir / "subs.xml");
794 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
797 check_verify_result (
800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
801 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
803 dcp::VerificationNote::Type::ERROR,
804 dcp::VerificationNote::Code::INVALID_XML,
805 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
809 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
814 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
816 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
817 prepare_directory(dir);
818 copy_file("test/data/subs4.xml", dir / "subs.xml");
819 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
820 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
821 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
823 check_verify_result (
826 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
827 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
828 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
834 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
836 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
837 prepare_directory(dir);
838 copy_file("test/data/subs5.xml", dir / "subs.xml");
839 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
840 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
841 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
843 check_verify_result (
846 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
847 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
853 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
855 path const dir("build/test/verify_valid_smpte_subtitles");
856 prepare_directory (dir);
857 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
858 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
859 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
860 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
866 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} }
871 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
873 using namespace boost::filesystem;
875 path const dir("build/test/verify_invalid_smpte_subtitles");
876 prepare_directory (dir);
877 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
878 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
879 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
880 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
881 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
883 check_verify_result (
886 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
888 dcp::VerificationNote::Type::ERROR,
889 dcp::VerificationNote::Code::INVALID_XML,
890 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
896 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} }
901 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
903 path const dir("build/test/verify_empty_text_node_in_subtitles");
904 prepare_directory (dir);
905 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
906 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
907 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
908 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
910 check_verify_result (
913 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
914 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
917 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} }
922 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
923 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
925 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
926 prepare_directory (dir);
927 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
928 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
929 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
930 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
932 check_verify_result (
935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
936 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
941 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
942 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
944 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
945 prepare_directory (dir);
946 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
947 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
948 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
949 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
951 check_verify_result (
954 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
955 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
956 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
957 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
962 BOOST_AUTO_TEST_CASE (verify_external_asset)
964 path const ov_dir("build/test/verify_external_asset");
965 prepare_directory (ov_dir);
967 auto image = black_image ();
968 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
969 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
970 dcp_from_frame (frame, ov_dir);
972 dcp::DCP ov (ov_dir);
975 path const vf_dir("build/test/verify_external_asset_vf");
976 prepare_directory (vf_dir);
978 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
979 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
981 check_verify_result (
984 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
990 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
992 path const dir("build/test/verify_valid_cpl_metadata");
993 prepare_directory (dir);
995 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
996 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
997 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
999 auto reel = make_shared<dcp::Reel>();
1000 reel->add (reel_asset);
1002 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1003 reel->add (simple_markers(16 * 24));
1005 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1007 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1008 cpl->set_main_sound_sample_rate (48000);
1009 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1010 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1011 cpl->set_version_number (1);
1015 dcp.set_annotation_text("hello");
1021 find_prefix(path dir, string prefix)
1023 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1024 return boost::starts_with(p.filename().string(), prefix);
1027 BOOST_REQUIRE(iter != directory_iterator());
1028 return iter->path();
1032 path find_cpl (path dir)
1034 return find_prefix(dir, "cpl_");
1041 return find_prefix(dir, "pkl_");
1046 find_asset_map(path dir)
1048 return find_prefix(dir, "ASSETMAP");
1052 /* DCP with invalid CompositionMetadataAsset */
1053 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1055 using namespace boost::filesystem;
1057 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1058 prepare_directory (dir);
1060 auto reel = make_shared<dcp::Reel>();
1061 reel->add (black_picture_asset(dir));
1062 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1064 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1065 cpl->set_main_sound_sample_rate (48000);
1066 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1067 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1068 cpl->set_version_number (1);
1070 reel->add (simple_markers());
1074 dcp.set_annotation_text("hello");
1078 Editor e (find_cpl(dir));
1079 e.replace ("MainSound", "MainSoundX");
1082 check_verify_result (
1085 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1086 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1088 dcp::VerificationNote::Type::ERROR,
1089 dcp::VerificationNote::Code::INVALID_XML,
1090 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1091 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1092 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1093 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1094 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1095 "ExtensionMetadataList?,)'"),
1096 canonical(cpl->file().get()),
1099 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1104 /* DCP with invalid CompositionMetadataAsset */
1105 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1107 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1108 prepare_directory (dir);
1110 auto reel = make_shared<dcp::Reel>();
1111 reel->add (black_picture_asset(dir));
1112 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1114 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1115 cpl->set_main_sound_sample_rate (48000);
1116 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1117 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1121 dcp.set_annotation_text("hello");
1125 Editor e (find_cpl(dir));
1126 e.replace ("meta:Width", "meta:WidthX");
1129 check_verify_result (
1131 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1136 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1138 path const dir("build/test/verify_invalid_language1");
1139 prepare_directory (dir);
1140 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1141 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1142 asset->_language = "wrong-andbad";
1143 asset->write (dir / "subs.mxf");
1144 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1145 reel_asset->_language = "badlang";
1146 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1148 check_verify_result (
1151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1152 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1153 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1158 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1159 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1161 path const dir("build/test/verify_invalid_language2");
1162 prepare_directory (dir);
1163 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1164 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1165 asset->_language = "wrong-andbad";
1166 asset->write (dir / "subs.mxf");
1167 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1168 reel_asset->_language = "badlang";
1169 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1171 check_verify_result (
1174 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1175 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1176 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1181 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1182 * the release territory.
1184 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1186 path const dir("build/test/verify_invalid_language3");
1187 prepare_directory (dir);
1189 auto picture = simple_picture (dir, "foo");
1190 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1191 auto reel = make_shared<dcp::Reel>();
1192 reel->add (reel_picture);
1193 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1194 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1195 reel->add (reel_sound);
1196 reel->add (simple_markers());
1198 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1200 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1201 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1202 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1203 cpl->set_main_sound_sample_rate (48000);
1204 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1205 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1206 cpl->set_version_number (1);
1207 cpl->_release_territory = "fred-jim";
1208 auto dcp = make_shared<dcp::DCP>(dir);
1210 dcp->set_annotation_text("hello");
1213 check_verify_result (
1216 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1217 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1218 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1219 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1225 vector<dcp::VerificationNote>
1226 check_picture_size (int width, int height, int frame_rate, bool three_d)
1228 using namespace boost::filesystem;
1230 path dcp_path = "build/test/verify_picture_test";
1231 prepare_directory (dcp_path);
1233 shared_ptr<dcp::PictureAsset> mp;
1235 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1237 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1239 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1241 auto image = black_image (dcp::Size(width, height));
1242 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1243 int const length = three_d ? frame_rate * 2 : frame_rate;
1244 for (int i = 0; i < length; ++i) {
1245 picture_writer->write (j2c.data(), j2c.size());
1247 picture_writer->finalize ();
1249 auto d = make_shared<dcp::DCP>(dcp_path);
1250 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1251 cpl->set_annotation_text ("A Test DCP");
1252 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1253 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1254 cpl->set_main_sound_sample_rate (48000);
1255 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1256 cpl->set_main_picture_active_area(dcp::Size(width, height));
1257 cpl->set_version_number (1);
1259 auto reel = make_shared<dcp::Reel>();
1262 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1264 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1267 reel->add (simple_markers(frame_rate));
1272 d->set_annotation_text("A Test DCP");
1275 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1281 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1283 auto notes = check_picture_size(width, height, frame_rate, three_d);
1284 BOOST_CHECK_EQUAL (notes.size(), 0U);
1290 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1292 auto notes = check_picture_size(width, height, frame_rate, three_d);
1293 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1294 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1295 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1301 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1303 auto notes = check_picture_size(width, height, frame_rate, three_d);
1304 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1305 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1306 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1312 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1314 auto notes = check_picture_size(width, height, frame_rate, three_d);
1315 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1316 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1317 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1321 BOOST_AUTO_TEST_CASE (verify_picture_size)
1323 using namespace boost::filesystem;
1326 check_picture_size_ok (2048, 858, 24, false);
1327 check_picture_size_ok (2048, 858, 25, false);
1328 check_picture_size_ok (2048, 858, 48, false);
1329 check_picture_size_ok (2048, 858, 24, true);
1330 check_picture_size_ok (2048, 858, 25, true);
1331 check_picture_size_ok (2048, 858, 48, true);
1334 check_picture_size_ok (1998, 1080, 24, false);
1335 check_picture_size_ok (1998, 1080, 25, false);
1336 check_picture_size_ok (1998, 1080, 48, false);
1337 check_picture_size_ok (1998, 1080, 24, true);
1338 check_picture_size_ok (1998, 1080, 25, true);
1339 check_picture_size_ok (1998, 1080, 48, true);
1342 check_picture_size_ok (4096, 1716, 24, false);
1345 check_picture_size_ok (3996, 2160, 24, false);
1347 /* Bad frame size */
1348 check_picture_size_bad_frame_size (2050, 858, 24, false);
1349 check_picture_size_bad_frame_size (2048, 658, 25, false);
1350 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1351 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1353 /* Bad 2K frame rate */
1354 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1355 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1356 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1358 /* Bad 4K frame rate */
1359 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1360 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1363 auto notes = check_picture_size(3996, 2160, 24, true);
1364 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1365 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1366 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1372 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")
1375 std::make_shared<dcp::SubtitleString>(
1383 dcp::Time(start_frame, 24, 24),
1384 dcp::Time(end_frame, 24, 24),
1386 dcp::HAlign::CENTER,
1390 dcp::Direction::LTR,
1402 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1404 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1405 prepare_directory (dir);
1407 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1408 for (int i = 0; i < 2048; ++i) {
1409 add_test_subtitle (asset, i * 24, i * 24 + 20);
1411 asset->set_language (dcp::LanguageTag("de-DE"));
1412 asset->write (dir / "subs.mxf");
1413 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1414 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1416 check_verify_result (
1419 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1421 dcp::VerificationNote::Type::BV21_ERROR,
1422 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1424 canonical(dir / "subs.mxf")
1426 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1427 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1433 shared_ptr<dcp::SMPTESubtitleAsset>
1434 make_large_subtitle_asset (path font_file)
1436 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1437 dcp::ArrayData big_fake_font(1024 * 1024);
1438 big_fake_font.write (font_file);
1439 for (int i = 0; i < 116; ++i) {
1440 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1448 verify_timed_text_asset_too_large (string name)
1450 auto const dir = path("build/test") / name;
1451 prepare_directory (dir);
1452 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1453 add_test_subtitle (asset, 0, 240);
1454 asset->set_language (dcp::LanguageTag("de-DE"));
1455 asset->write (dir / "subs.mxf");
1457 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1458 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1460 check_verify_result (
1463 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695532"), canonical(dir / "subs.mxf") },
1464 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1465 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1466 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1467 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1472 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1474 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1475 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1479 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1481 path dir = "build/test/verify_missing_subtitle_language";
1482 prepare_directory (dir);
1483 auto dcp = make_simple (dir, 1, 106);
1486 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1487 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1488 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1489 "<ContentTitleText>Content</ContentTitleText>"
1490 "<AnnotationText>Annotation</AnnotationText>"
1491 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1492 "<ReelNumber>1</ReelNumber>"
1493 "<EditRate>24 1</EditRate>"
1494 "<TimeCodeRate>24</TimeCodeRate>"
1495 "<StartTime>00:00:00:00</StartTime>"
1496 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1498 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1499 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1500 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1506 dcp::File xml_file(dir / "subs.xml", "w");
1507 BOOST_REQUIRE (xml_file);
1508 xml_file.write(xml.c_str(), xml.size(), 1);
1510 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1511 subs->write (dir / "subs.mxf");
1513 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1514 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1515 dcp->set_annotation_text("A Test DCP");
1518 check_verify_result (
1521 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1522 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1527 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1529 path path ("build/test/verify_mismatched_subtitle_languages");
1530 auto constexpr reel_length = 192;
1531 auto dcp = make_simple (path, 2, reel_length);
1532 auto cpl = dcp->cpls()[0];
1535 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1536 subs->set_language (dcp::LanguageTag("de-DE"));
1537 subs->add (simple_subtitle());
1538 subs->write (path / "subs1.mxf");
1539 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1540 cpl->reels()[0]->add(reel_subs);
1544 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1545 subs->set_language (dcp::LanguageTag("en-US"));
1546 subs->add (simple_subtitle());
1547 subs->write (path / "subs2.mxf");
1548 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1549 cpl->reels()[1]->add(reel_subs);
1552 dcp->set_annotation_text("A Test DCP");
1555 check_verify_result (
1558 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1559 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1560 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1565 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1567 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1568 auto constexpr reel_length = 192;
1569 auto dcp = make_simple (path, 2, reel_length);
1570 auto cpl = dcp->cpls()[0];
1573 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1574 ccaps->set_language (dcp::LanguageTag("de-DE"));
1575 ccaps->add (simple_subtitle());
1576 ccaps->write (path / "subs1.mxf");
1577 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1578 cpl->reels()[0]->add(reel_ccaps);
1582 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1583 ccaps->set_language (dcp::LanguageTag("en-US"));
1584 ccaps->add (simple_subtitle());
1585 ccaps->write (path / "subs2.mxf");
1586 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1587 cpl->reels()[1]->add(reel_ccaps);
1590 dcp->set_annotation_text("A Test DCP");
1593 check_verify_result (
1596 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1602 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1604 path dir = "build/test/verify_missing_subtitle_start_time";
1605 prepare_directory (dir);
1606 auto dcp = make_simple (dir, 1, 106);
1609 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1610 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1611 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1612 "<ContentTitleText>Content</ContentTitleText>"
1613 "<AnnotationText>Annotation</AnnotationText>"
1614 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1615 "<ReelNumber>1</ReelNumber>"
1616 "<Language>de-DE</Language>"
1617 "<EditRate>24 1</EditRate>"
1618 "<TimeCodeRate>24</TimeCodeRate>"
1619 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1621 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1622 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1623 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1629 dcp::File xml_file(dir / "subs.xml", "w");
1630 BOOST_REQUIRE (xml_file);
1631 xml_file.write(xml.c_str(), xml.size(), 1);
1633 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1634 subs->write (dir / "subs.mxf");
1636 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1637 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1638 dcp->set_annotation_text("A Test DCP");
1641 check_verify_result (
1644 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1645 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1650 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1652 path dir = "build/test/verify_invalid_subtitle_start_time";
1653 prepare_directory (dir);
1654 auto dcp = make_simple (dir, 1, 106);
1657 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1658 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1659 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1660 "<ContentTitleText>Content</ContentTitleText>"
1661 "<AnnotationText>Annotation</AnnotationText>"
1662 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1663 "<ReelNumber>1</ReelNumber>"
1664 "<Language>de-DE</Language>"
1665 "<EditRate>24 1</EditRate>"
1666 "<TimeCodeRate>24</TimeCodeRate>"
1667 "<StartTime>00:00:02:00</StartTime>"
1668 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1670 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1671 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1672 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1678 dcp::File xml_file(dir / "subs.xml", "w");
1679 BOOST_REQUIRE (xml_file);
1680 xml_file.write(xml.c_str(), xml.size(), 1);
1682 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1683 subs->write (dir / "subs.mxf");
1685 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1686 dcp->cpls().front()->reels().front()->add(reel_subs);
1687 dcp->set_annotation_text("A Test DCP");
1690 check_verify_result (
1693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1702 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1705 , v_position(v_position_)
1713 dcp::VAlign v_align;
1719 shared_ptr<dcp::CPL>
1720 dcp_with_text (path dir, vector<TestText> subs)
1722 prepare_directory (dir);
1723 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1724 asset->set_start_time (dcp::Time());
1725 for (auto i: subs) {
1726 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1728 asset->set_language (dcp::LanguageTag("de-DE"));
1729 asset->write (dir / "subs.mxf");
1731 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1732 return write_dcp_with_single_asset (dir, reel_asset);
1737 shared_ptr<dcp::CPL>
1738 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1740 prepare_directory (dir);
1741 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1742 asset->set_start_time (dcp::Time());
1743 asset->set_language (dcp::LanguageTag("de-DE"));
1745 auto subs_mxf = dir / "subs.mxf";
1746 asset->write (subs_mxf);
1748 /* The call to write() puts the asset into the DCP correctly but it will have
1749 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1752 ASDCP::TimedText::MXFWriter writer;
1753 ASDCP::WriterInfo writer_info;
1754 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1756 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1757 DCP_ASSERT (c == Kumu::UUID_Length);
1758 ASDCP::TimedText::TimedTextDescriptor descriptor;
1759 descriptor.ContainerDuration = asset->intrinsic_duration();
1760 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1761 DCP_ASSERT (c == Kumu::UUID_Length);
1762 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1763 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1764 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1765 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1768 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1769 return write_dcp_with_single_asset (dir, reel_asset);
1773 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1775 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1776 /* Just too early */
1777 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1778 check_verify_result (
1781 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1782 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1788 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1790 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1791 /* Just late enough */
1792 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1793 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1797 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1799 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1800 prepare_directory (dir);
1802 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1803 asset1->set_start_time (dcp::Time());
1804 /* Just late enough */
1805 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1806 asset1->set_language (dcp::LanguageTag("de-DE"));
1807 asset1->write (dir / "subs1.mxf");
1808 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1809 auto reel1 = make_shared<dcp::Reel>();
1810 reel1->add (reel_asset1);
1811 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1812 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1813 reel1->add (markers1);
1815 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1816 asset2->set_start_time (dcp::Time());
1817 /* This would be too early on first reel but should be OK on the second */
1818 add_test_subtitle (asset2, 3, 4 * 24);
1819 asset2->set_language (dcp::LanguageTag("de-DE"));
1820 asset2->write (dir / "subs2.mxf");
1821 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1822 auto reel2 = make_shared<dcp::Reel>();
1823 reel2->add (reel_asset2);
1824 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1825 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1826 reel2->add (markers2);
1828 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1831 auto dcp = make_shared<dcp::DCP>(dir);
1833 dcp->set_annotation_text("hello");
1836 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1840 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1842 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1843 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1847 { 5 * 24 + 1, 6 * 24 },
1849 check_verify_result (
1852 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1858 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1860 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1861 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1865 { 5 * 24 + 16, 8 * 24 },
1867 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1871 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1873 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1874 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1875 check_verify_result (
1878 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1879 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1884 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1886 auto const dir = path("build/test/verify_valid_subtitle_duration");
1887 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1888 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1892 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1894 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1895 prepare_directory (dir);
1896 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1897 asset->set_start_time (dcp::Time());
1898 add_test_subtitle (asset, 0, 4 * 24);
1899 asset->set_language (dcp::LanguageTag("de-DE"));
1900 asset->write (dir / "subs.mxf");
1902 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1903 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1904 check_verify_result (
1907 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1908 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1909 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1910 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1916 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1918 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1919 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1922 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1923 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1924 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1925 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1927 check_verify_result (
1930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1936 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1938 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1939 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1942 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1943 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1944 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1946 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1950 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1952 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1953 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1956 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1957 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1958 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1959 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1961 check_verify_result (
1964 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1965 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1970 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1972 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1973 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1976 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1977 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1978 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1979 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1981 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1985 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1987 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1988 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1991 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1993 check_verify_result (
1996 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1997 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2002 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2004 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2005 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2008 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2010 check_verify_result (
2013 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2019 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2021 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2022 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2025 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2026 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2027 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2028 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2030 check_verify_result (
2033 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2034 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2039 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2041 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2042 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2045 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2046 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2047 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2049 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2053 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2055 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2056 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2059 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2060 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2061 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2062 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2064 check_verify_result (
2067 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2068 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2073 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2075 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2076 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2079 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2080 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2081 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2082 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2084 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2088 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2090 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2091 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2094 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2096 check_verify_result (
2099 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2104 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2106 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2107 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2110 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2112 check_verify_result (
2115 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2121 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2123 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2124 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2127 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2128 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2129 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2131 check_verify_result (
2134 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2139 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2141 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2142 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2145 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2146 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2147 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2149 check_verify_result (
2152 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2153 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2158 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2160 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2161 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2164 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2165 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2166 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2168 check_verify_result (
2171 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2176 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2178 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2179 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2182 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2183 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2184 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2186 check_verify_result (
2189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2194 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2196 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2197 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2198 check_verify_result (
2201 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2202 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2207 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2209 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2210 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2211 check_verify_result (
2214 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2220 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2222 path const dir("build/test/verify_invalid_sound_frame_rate");
2223 prepare_directory (dir);
2225 auto picture = simple_picture (dir, "foo");
2226 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2227 auto reel = make_shared<dcp::Reel>();
2228 reel->add (reel_picture);
2229 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2230 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2231 reel->add (reel_sound);
2232 reel->add (simple_markers());
2233 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2235 auto dcp = make_shared<dcp::DCP>(dir);
2237 dcp->set_annotation_text("hello");
2240 check_verify_result (
2243 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2244 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2249 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2251 path const dir("build/test/verify_missing_cpl_annotation_text");
2252 auto dcp = make_simple (dir);
2253 dcp->set_annotation_text("A Test DCP");
2256 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2258 auto const cpl = dcp->cpls()[0];
2261 BOOST_REQUIRE (cpl->file());
2262 Editor e(cpl->file().get());
2263 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2266 check_verify_result (
2269 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2270 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2275 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2277 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2278 auto dcp = make_simple (dir);
2279 dcp->set_annotation_text("A Test DCP");
2282 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2283 auto const cpl = dcp->cpls()[0];
2286 BOOST_REQUIRE (cpl->file());
2287 Editor e(cpl->file().get());
2288 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2291 check_verify_result (
2294 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2295 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2300 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2302 path const dir("build/test/verify_mismatched_asset_duration");
2303 prepare_directory (dir);
2304 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2305 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2307 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2308 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2310 auto reel = make_shared<dcp::Reel>(
2311 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2312 make_shared<dcp::ReelSoundAsset>(ms, 0)
2315 reel->add (simple_markers());
2319 dcp->set_annotation_text("A Test DCP");
2322 check_verify_result (
2325 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2326 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2333 shared_ptr<dcp::CPL>
2334 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2336 prepare_directory (dir);
2337 auto dcp = make_shared<dcp::DCP>(dir);
2338 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2340 auto constexpr reel_length = 192;
2342 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2343 subs->set_language (dcp::LanguageTag("de-DE"));
2344 subs->set_start_time (dcp::Time());
2345 subs->add (simple_subtitle());
2346 subs->write (dir / "subs.mxf");
2347 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2349 auto reel1 = make_shared<dcp::Reel>(
2350 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2351 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2355 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2358 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2359 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2360 reel1->add (markers1);
2364 auto reel2 = make_shared<dcp::Reel>(
2365 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2366 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2370 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2373 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2374 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2375 reel2->add (markers2);
2380 dcp->set_annotation_text("A Test DCP");
2387 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2390 path dir ("build/test/missing_main_subtitle_from_some_reels");
2391 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2392 check_verify_result (
2395 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2396 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2402 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2403 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2404 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2408 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2409 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2410 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2416 shared_ptr<dcp::CPL>
2417 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2419 prepare_directory (dir);
2420 auto dcp = make_shared<dcp::DCP>(dir);
2421 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2423 auto constexpr reel_length = 192;
2425 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2426 subs->set_language (dcp::LanguageTag("de-DE"));
2427 subs->set_start_time (dcp::Time());
2428 subs->add (simple_subtitle());
2429 subs->write (dir / "subs.mxf");
2431 auto reel1 = make_shared<dcp::Reel>(
2432 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2433 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2436 for (int i = 0; i < caps_in_reel1; ++i) {
2437 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2440 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2441 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2442 reel1->add (markers1);
2446 auto reel2 = make_shared<dcp::Reel>(
2447 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2448 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2451 for (int i = 0; i < caps_in_reel2; ++i) {
2452 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2455 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2456 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2457 reel2->add (markers2);
2462 dcp->set_annotation_text("A Test DCP");
2469 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2472 path dir ("build/test/mismatched_closed_caption_asset_counts");
2473 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2474 check_verify_result (
2477 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2478 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2483 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2484 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2485 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2489 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2490 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2491 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2498 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2500 prepare_directory (dir);
2501 auto dcp = make_shared<dcp::DCP>(dir);
2502 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2504 auto constexpr reel_length = 192;
2506 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2507 subs->set_language (dcp::LanguageTag("de-DE"));
2508 subs->set_start_time (dcp::Time());
2509 subs->add (simple_subtitle());
2510 subs->write (dir / "subs.mxf");
2511 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2514 auto reel = make_shared<dcp::Reel>(
2515 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2516 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2519 reel->add (reel_text);
2521 reel->add (simple_markers(reel_length));
2526 dcp->set_annotation_text("A Test DCP");
2529 check_verify_result (
2532 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2533 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2538 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2540 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2541 "build/test/verify_subtitle_entry_point_must_be_present",
2542 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2543 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2544 asset->unset_entry_point ();
2548 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2549 "build/test/verify_subtitle_entry_point_must_be_zero",
2550 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2551 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2552 asset->set_entry_point (4);
2556 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2557 "build/test/verify_closed_caption_entry_point_must_be_present",
2558 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2559 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2560 asset->unset_entry_point ();
2564 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2565 "build/test/verify_closed_caption_entry_point_must_be_zero",
2566 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2567 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2568 asset->set_entry_point (9);
2574 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2578 path const dir("build/test/verify_missing_hash");
2579 auto dcp = make_simple (dir);
2580 dcp->set_annotation_text("A Test DCP");
2583 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2584 auto const cpl = dcp->cpls()[0];
2585 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2586 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2587 auto asset_id = cpl->reels()[0]->main_picture()->id();
2590 BOOST_REQUIRE (cpl->file());
2591 Editor e(cpl->file().get());
2592 e.delete_first_line_containing("<Hash>");
2595 check_verify_result (
2598 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2599 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2606 verify_markers_test (
2608 vector<pair<dcp::Marker, dcp::Time>> markers,
2609 vector<dcp::VerificationNote> test_notes
2612 auto dcp = make_simple (dir);
2613 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2614 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2615 for (auto const& i: markers) {
2616 markers_asset->set (i.first, i.second);
2618 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2619 dcp->set_annotation_text("A Test DCP");
2622 check_verify_result ({dir}, test_notes);
2626 BOOST_AUTO_TEST_CASE (verify_markers)
2628 verify_markers_test (
2629 "build/test/verify_markers_all_correct",
2631 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2632 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2633 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2634 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2639 verify_markers_test (
2640 "build/test/verify_markers_missing_ffec",
2642 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2643 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2644 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2647 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2650 verify_markers_test (
2651 "build/test/verify_markers_missing_ffmc",
2653 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2654 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2655 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2658 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2661 verify_markers_test (
2662 "build/test/verify_markers_missing_ffoc",
2664 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2665 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2666 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2669 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2672 verify_markers_test (
2673 "build/test/verify_markers_missing_lfoc",
2675 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2676 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2677 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2680 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2683 verify_markers_test (
2684 "build/test/verify_markers_incorrect_ffoc",
2686 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2687 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2688 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2689 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2692 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2695 verify_markers_test (
2696 "build/test/verify_markers_incorrect_lfoc",
2698 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2699 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2700 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2701 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2704 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2709 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2711 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2712 prepare_directory (dir);
2713 auto dcp = make_simple (dir);
2714 auto cpl = dcp->cpls()[0];
2715 cpl->unset_version_number();
2716 dcp->set_annotation_text("A Test DCP");
2719 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2723 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2725 path dir = "build/test/verify_missing_extension_metadata1";
2726 auto dcp = make_simple (dir);
2727 dcp->set_annotation_text("A Test DCP");
2730 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2731 auto cpl = dcp->cpls()[0];
2734 Editor e (cpl->file().get());
2735 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2738 check_verify_result (
2741 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2742 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2747 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2749 path dir = "build/test/verify_missing_extension_metadata2";
2750 auto dcp = make_simple (dir);
2751 dcp->set_annotation_text("A Test DCP");
2754 auto cpl = dcp->cpls()[0];
2757 Editor e (cpl->file().get());
2758 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2761 check_verify_result (
2764 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2765 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2770 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2772 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2773 auto dcp = make_simple (dir);
2774 dcp->set_annotation_text("A Test DCP");
2777 auto const cpl = dcp->cpls()[0];
2780 Editor e (cpl->file().get());
2781 e.replace ("<meta:Name>A", "<meta:NameX>A");
2782 e.replace ("n</meta:Name>", "n</meta:NameX>");
2785 check_verify_result (
2788 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2789 { 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 },
2790 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2795 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2797 path dir = "build/test/verify_invalid_extension_metadata1";
2798 auto dcp = make_simple (dir);
2799 dcp->set_annotation_text("A Test DCP");
2802 auto cpl = dcp->cpls()[0];
2805 Editor e (cpl->file().get());
2806 e.replace ("Application", "Fred");
2809 check_verify_result (
2812 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2813 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2818 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2820 path dir = "build/test/verify_invalid_extension_metadata2";
2821 auto dcp = make_simple (dir);
2822 dcp->set_annotation_text("A Test DCP");
2825 auto cpl = dcp->cpls()[0];
2828 Editor e (cpl->file().get());
2829 e.replace ("DCP Constraints Profile", "Fred");
2832 check_verify_result (
2835 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2836 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2841 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2843 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2844 auto dcp = make_simple (dir);
2845 dcp->set_annotation_text("A Test DCP");
2848 auto const cpl = dcp->cpls()[0];
2851 Editor e (cpl->file().get());
2852 e.replace ("<meta:Value>", "<meta:ValueX>");
2853 e.replace ("</meta:Value>", "</meta:ValueX>");
2856 check_verify_result (
2859 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2860 { 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 },
2861 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2866 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2868 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2869 auto dcp = make_simple (dir);
2870 dcp->set_annotation_text("A Test DCP");
2873 auto const cpl = dcp->cpls()[0];
2876 Editor e (cpl->file().get());
2877 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2880 check_verify_result (
2883 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2884 { 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() },
2889 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2891 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2892 auto dcp = make_simple (dir);
2893 dcp->set_annotation_text("A Test DCP");
2896 auto const cpl = dcp->cpls()[0];
2899 Editor e (cpl->file().get());
2900 e.replace ("<meta:Property>", "<meta:PropertyX>");
2901 e.replace ("</meta:Property>", "</meta:PropertyX>");
2904 check_verify_result (
2907 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2908 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2909 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2914 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2916 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2917 auto dcp = make_simple (dir);
2918 dcp->set_annotation_text("A Test DCP");
2921 auto const cpl = dcp->cpls()[0];
2924 Editor e (cpl->file().get());
2925 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2926 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2929 check_verify_result (
2932 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2933 { 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 },
2934 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2940 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2942 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2943 prepare_directory (dir);
2944 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2945 copy_file (i.path(), dir / i.path().filename());
2948 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2949 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2953 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2956 check_verify_result (
2959 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2960 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2961 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2962 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2963 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2964 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2965 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2971 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2973 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2974 prepare_directory (dir);
2975 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2976 copy_file (i.path(), dir / i.path().filename());
2979 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2980 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2983 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2986 check_verify_result (
2989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2992 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2993 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2994 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
3000 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3002 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3003 prepare_directory (dir);
3004 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3005 copy_file (i.path(), dir / i.path().filename());
3009 Editor e (dir / dcp_test1_pkl);
3010 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3013 check_verify_result ({dir}, {});
3017 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3019 path dir ("build/test/verify_must_not_be_partially_encrypted");
3020 prepare_directory (dir);
3024 auto signer = make_shared<dcp::CertificateChain>();
3025 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3026 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3027 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3028 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3030 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3034 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3037 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3038 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3039 for (int i = 0; i < 24; ++i) {
3040 writer->write (j2c.data(), j2c.size());
3042 writer->finalize ();
3044 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3046 auto reel = make_shared<dcp::Reel>(
3047 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3048 make_shared<dcp::ReelSoundAsset>(ms, 0)
3051 reel->add (simple_markers());
3055 cpl->set_content_version (
3056 {"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"}
3058 cpl->set_annotation_text ("A Test DCP");
3059 cpl->set_issuer ("OpenDCP 0.0.25");
3060 cpl->set_creator ("OpenDCP 0.0.25");
3061 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3062 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3063 cpl->set_main_sound_sample_rate (48000);
3064 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3065 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3066 cpl->set_version_number (1);
3070 d.set_issuer("OpenDCP 0.0.25");
3071 d.set_creator("OpenDCP 0.0.25");
3072 d.set_issue_date("2012-07-17T04:45:18+00:00");
3073 d.set_annotation_text("A Test DCP");
3074 d.write_xml(signer);
3076 check_verify_result (
3079 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3084 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3086 vector<dcp::VerificationNote> notes;
3087 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"));
3088 auto reader = picture.start_read ();
3089 auto frame = reader->get_frame (0);
3090 verify_j2k (frame, notes);
3091 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3095 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3097 vector<dcp::VerificationNote> notes;
3098 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3099 auto reader = picture.start_read ();
3100 auto frame = reader->get_frame (0);
3101 verify_j2k (frame, notes);
3102 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3106 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3108 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3109 prepare_directory (dir);
3110 auto dcp = make_simple (dir);
3112 vector<dcp::VerificationNote> notes;
3113 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3114 auto reader = picture.start_read ();
3115 auto frame = reader->get_frame (0);
3116 verify_j2k (frame, notes);
3117 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3121 /** Check that ResourceID and the XML ID being different is spotted */
3122 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3124 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3125 prepare_directory (dir);
3127 ASDCP::WriterInfo writer_info;
3128 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3131 auto mxf_id = dcp::make_uuid ();
3132 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3133 BOOST_REQUIRE (c == Kumu::UUID_Length);
3135 auto resource_id = dcp::make_uuid ();
3136 ASDCP::TimedText::TimedTextDescriptor descriptor;
3137 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3138 DCP_ASSERT (c == Kumu::UUID_Length);
3140 auto xml_id = dcp::make_uuid ();
3141 ASDCP::TimedText::MXFWriter writer;
3142 auto subs_mxf = dir / "subs.mxf";
3143 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3144 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3145 writer.WriteTimedTextResource (dcp::String::compose(
3146 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3147 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3148 "<Id>urn:uuid:%1</Id>"
3149 "<ContentTitleText>Content</ContentTitleText>"
3150 "<AnnotationText>Annotation</AnnotationText>"
3151 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3152 "<ReelNumber>1</ReelNumber>"
3153 "<Language>en-US</Language>"
3154 "<EditRate>25 1</EditRate>"
3155 "<TimeCodeRate>25</TimeCodeRate>"
3156 "<StartTime>00:00:00:00</StartTime>"
3158 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3159 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3160 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3169 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3170 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3172 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3174 check_verify_result (
3177 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3178 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3179 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3180 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3185 /** Check that ResourceID and the MXF ID being the same is spotted */
3186 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3188 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3189 prepare_directory (dir);
3191 ASDCP::WriterInfo writer_info;
3192 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3195 auto mxf_id = dcp::make_uuid ();
3196 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3197 BOOST_REQUIRE (c == Kumu::UUID_Length);
3199 auto resource_id = mxf_id;
3200 ASDCP::TimedText::TimedTextDescriptor descriptor;
3201 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3202 DCP_ASSERT (c == Kumu::UUID_Length);
3204 auto xml_id = resource_id;
3205 ASDCP::TimedText::MXFWriter writer;
3206 auto subs_mxf = dir / "subs.mxf";
3207 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3208 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3209 writer.WriteTimedTextResource (dcp::String::compose(
3210 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3211 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3212 "<Id>urn:uuid:%1</Id>"
3213 "<ContentTitleText>Content</ContentTitleText>"
3214 "<AnnotationText>Annotation</AnnotationText>"
3215 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3216 "<ReelNumber>1</ReelNumber>"
3217 "<Language>en-US</Language>"
3218 "<EditRate>25 1</EditRate>"
3219 "<TimeCodeRate>25</TimeCodeRate>"
3220 "<StartTime>00:00:00:00</StartTime>"
3222 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3223 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3224 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3233 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3234 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3236 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3238 check_verify_result (
3241 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3242 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3243 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3244 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3245 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3250 /** Check a DCP with a 3D asset marked as 2D */
3251 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3253 check_verify_result (
3254 { private_test / "data" / "xm" },
3257 dcp::VerificationNote::Type::WARNING,
3258 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3261 dcp::VerificationNote::Type::BV21_ERROR,
3262 dcp::VerificationNote::Code::INVALID_STANDARD
3269 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3271 path dir = "build/test/verify_unexpected_things_in_main_markers";
3272 prepare_directory (dir);
3273 auto dcp = make_simple (dir, 1, 24);
3274 dcp->set_annotation_text("A Test DCP");
3278 Editor e (find_cpl(dir));
3280 " <IntrinsicDuration>24</IntrinsicDuration>",
3281 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3285 dcp::CPL cpl (find_cpl(dir));
3287 check_verify_result (
3290 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3291 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3292 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3297 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3299 path dir = "build/test/verify_invalid_content_kind";
3300 prepare_directory (dir);
3301 auto dcp = make_simple (dir, 1, 24);
3302 dcp->set_annotation_text("A Test DCP");
3306 Editor e(find_cpl(dir));
3307 e.replace("trailer", "trip");
3310 dcp::CPL cpl (find_cpl(dir));
3312 check_verify_result (
3315 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3316 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3322 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3324 path dir = "build/test/verify_valid_content_kind";
3325 prepare_directory (dir);
3326 auto dcp = make_simple (dir, 1, 24);
3327 dcp->set_annotation_text("A Test DCP");
3331 Editor e(find_cpl(dir));
3332 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3335 dcp::CPL cpl (find_cpl(dir));
3337 check_verify_result (
3340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3346 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3348 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3349 prepare_directory(dir);
3350 auto dcp = make_simple(dir, 1, 24);
3353 auto constexpr area = "<meta:MainPictureActiveArea>";
3356 Editor e(find_cpl(dir));
3357 e.delete_lines_after(area, 2);
3358 e.insert(area, "<meta:Height>4080</meta:Height>");
3359 e.insert(area, "<meta:Width>1997</meta:Width>");
3362 dcp::PKL pkl(find_pkl(dir));
3363 dcp::CPL cpl(find_cpl(dir));
3365 check_verify_result(
3368 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3369 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3370 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3371 { 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)) },
3376 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3378 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3379 prepare_directory(dir);
3380 auto dcp = make_simple(dir, 1, 24);
3383 auto constexpr area = "<meta:MainPictureActiveArea>";
3386 Editor e(find_cpl(dir));
3387 e.delete_lines_after(area, 2);
3388 e.insert(area, "<meta:Height>5125</meta:Height>");
3389 e.insert(area, "<meta:Width>9900</meta:Width>");
3392 dcp::PKL pkl(find_pkl(dir));
3393 dcp::CPL cpl(find_cpl(dir));
3395 check_verify_result(
3398 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3399 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3400 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3401 { 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)) },
3402 { 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)) },
3407 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3411 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3412 prepare_directory(dir);
3413 auto dcp = make_simple(dir, 1, 24);
3417 Editor e(find_pkl(dir));
3418 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3421 dcp::PKL pkl(find_pkl(dir));
3423 check_verify_result(
3426 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3431 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3435 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3436 prepare_directory(dir);
3437 auto dcp = make_simple(dir, 1, 24);
3441 Editor e(find_asset_map(dir));
3442 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3445 dcp::PKL pkl(find_pkl(dir));
3446 dcp::AssetMap asset_map(find_asset_map(dir));
3448 check_verify_result(
3451 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3452 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3453 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3458 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3460 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3462 dcp::MXFMetadata mxf_meta;
3463 mxf_meta.company_name = "OpenDCP";
3464 mxf_meta.product_name = "OpenDCP";
3465 mxf_meta.product_version = "0.0.25";
3467 auto constexpr sample_rate = 48000;
3468 auto constexpr frames = 240;
3470 boost::filesystem::remove_all(path);
3471 boost::filesystem::create_directories(path);
3472 auto dcp = make_shared<dcp::DCP>(path);
3473 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3474 cpl->set_annotation_text("hello");
3475 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3476 cpl->set_main_sound_sample_rate(sample_rate);
3477 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3478 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3479 cpl->set_version_number(1);
3483 /* Reel with 2 channels of audio */
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));
3501 /* Reel with 6 channels of audio */
3503 auto mp = simple_picture(path, "2", frames, {});
3504 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3506 auto reel = make_shared<dcp::Reel>(
3507 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3508 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3511 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3512 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3519 dcp->set_annotation_text("hello");
3522 check_verify_result(
3525 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3530 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3532 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3534 dcp::MXFMetadata mxf_meta;
3535 mxf_meta.company_name = "OpenDCP";
3536 mxf_meta.product_name = "OpenDCP";
3537 mxf_meta.product_version = "0.0.25";
3539 auto constexpr sample_rate = 48000;
3540 auto constexpr frames = 240;
3542 boost::filesystem::remove_all(path);
3543 boost::filesystem::create_directories(path);
3544 auto dcp = make_shared<dcp::DCP>(path);
3545 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3546 cpl->set_annotation_text("hello");
3547 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3548 cpl->set_main_sound_sample_rate(sample_rate);
3549 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3550 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3551 cpl->set_version_number(1);
3553 auto mp = simple_picture(path, "1", frames, {});
3554 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3556 auto reel = make_shared<dcp::Reel>(
3557 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3558 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3561 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3562 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3563 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3569 dcp->set_annotation_text("hello");
3572 check_verify_result(
3575 { 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)) },