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 ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
777 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
779 using namespace boost::filesystem;
781 path const dir("build/test/verify_invalid_interop_subtitles");
782 prepare_directory (dir);
783 copy_file ("test/data/subs1.xml", dir / "subs.xml");
784 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
785 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
786 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
789 Editor e (dir / "subs.xml");
790 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
793 check_verify_result (
796 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
799 dcp::VerificationNote::Type::ERROR,
800 dcp::VerificationNote::Code::INVALID_XML,
801 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
809 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
811 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
812 prepare_directory(dir);
813 copy_file("test/data/subs4.xml", dir / "subs.xml");
814 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
815 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
816 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
818 check_verify_result (
821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
822 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
828 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
830 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
831 prepare_directory(dir);
832 copy_file("test/data/subs5.xml", dir / "subs.xml");
833 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
834 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
835 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
837 check_verify_result (
840 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
846 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
848 path const dir("build/test/verify_valid_smpte_subtitles");
849 prepare_directory (dir);
850 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
851 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
852 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
853 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
859 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} }
864 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
866 using namespace boost::filesystem;
868 path const dir("build/test/verify_invalid_smpte_subtitles");
869 prepare_directory (dir);
870 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
871 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
872 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
873 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
874 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
876 check_verify_result (
879 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
881 dcp::VerificationNote::Type::ERROR,
882 dcp::VerificationNote::Code::INVALID_XML,
883 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
889 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} }
894 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
896 path const dir("build/test/verify_empty_text_node_in_subtitles");
897 prepare_directory (dir);
898 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
899 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
900 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
901 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
903 check_verify_result (
906 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
907 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
908 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
909 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
910 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} }
915 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
916 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
918 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
919 prepare_directory (dir);
920 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
921 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
922 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
923 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
925 check_verify_result (
928 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
933 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
934 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
936 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
937 prepare_directory (dir);
938 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
939 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
940 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
941 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
943 check_verify_result (
946 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
948 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
953 BOOST_AUTO_TEST_CASE (verify_external_asset)
955 path const ov_dir("build/test/verify_external_asset");
956 prepare_directory (ov_dir);
958 auto image = black_image ();
959 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
960 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
961 dcp_from_frame (frame, ov_dir);
963 dcp::DCP ov (ov_dir);
966 path const vf_dir("build/test/verify_external_asset_vf");
967 prepare_directory (vf_dir);
969 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
970 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
972 check_verify_result (
975 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
976 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
981 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
983 path const dir("build/test/verify_valid_cpl_metadata");
984 prepare_directory (dir);
986 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
987 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
988 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
990 auto reel = make_shared<dcp::Reel>();
991 reel->add (reel_asset);
993 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
994 reel->add (simple_markers(16 * 24));
996 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
998 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
999 cpl->set_main_sound_sample_rate (48000);
1000 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1001 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1002 cpl->set_version_number (1);
1006 dcp.set_annotation_text("hello");
1012 find_prefix(path dir, string prefix)
1014 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1015 return boost::starts_with(p.filename().string(), prefix);
1018 BOOST_REQUIRE(iter != directory_iterator());
1019 return iter->path();
1023 path find_cpl (path dir)
1025 return find_prefix(dir, "cpl_");
1032 return find_prefix(dir, "pkl_");
1037 find_asset_map(path dir)
1039 return find_prefix(dir, "ASSETMAP");
1043 /* DCP with invalid CompositionMetadataAsset */
1044 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1046 using namespace boost::filesystem;
1048 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1049 prepare_directory (dir);
1051 auto reel = make_shared<dcp::Reel>();
1052 reel->add (black_picture_asset(dir));
1053 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1055 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1056 cpl->set_main_sound_sample_rate (48000);
1057 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1058 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1059 cpl->set_version_number (1);
1061 reel->add (simple_markers());
1065 dcp.set_annotation_text("hello");
1069 Editor e (find_cpl(dir));
1070 e.replace ("MainSound", "MainSoundX");
1073 check_verify_result (
1076 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1077 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1079 dcp::VerificationNote::Type::ERROR,
1080 dcp::VerificationNote::Code::INVALID_XML,
1081 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1082 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1083 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1084 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1085 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1086 "ExtensionMetadataList?,)'"),
1087 canonical(cpl->file().get()),
1090 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1095 /* DCP with invalid CompositionMetadataAsset */
1096 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1098 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1099 prepare_directory (dir);
1101 auto reel = make_shared<dcp::Reel>();
1102 reel->add (black_picture_asset(dir));
1103 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1105 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1106 cpl->set_main_sound_sample_rate (48000);
1107 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1108 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1112 dcp.set_annotation_text("hello");
1116 Editor e (find_cpl(dir));
1117 e.replace ("meta:Width", "meta:WidthX");
1120 check_verify_result (
1122 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1127 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1129 path const dir("build/test/verify_invalid_language1");
1130 prepare_directory (dir);
1131 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1132 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1133 asset->_language = "wrong-andbad";
1134 asset->write (dir / "subs.mxf");
1135 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1136 reel_asset->_language = "badlang";
1137 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1139 check_verify_result (
1142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1143 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1144 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1149 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1150 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1152 path const dir("build/test/verify_invalid_language2");
1153 prepare_directory (dir);
1154 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1155 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1156 asset->_language = "wrong-andbad";
1157 asset->write (dir / "subs.mxf");
1158 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1159 reel_asset->_language = "badlang";
1160 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1162 check_verify_result (
1165 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1166 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1167 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1172 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1173 * the release territory.
1175 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1177 path const dir("build/test/verify_invalid_language3");
1178 prepare_directory (dir);
1180 auto picture = simple_picture (dir, "foo");
1181 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1182 auto reel = make_shared<dcp::Reel>();
1183 reel->add (reel_picture);
1184 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1185 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1186 reel->add (reel_sound);
1187 reel->add (simple_markers());
1189 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1191 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1192 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1193 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1194 cpl->set_main_sound_sample_rate (48000);
1195 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1196 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1197 cpl->set_version_number (1);
1198 cpl->_release_territory = "fred-jim";
1199 auto dcp = make_shared<dcp::DCP>(dir);
1201 dcp->set_annotation_text("hello");
1204 check_verify_result (
1207 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1208 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1209 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1210 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1216 vector<dcp::VerificationNote>
1217 check_picture_size (int width, int height, int frame_rate, bool three_d)
1219 using namespace boost::filesystem;
1221 path dcp_path = "build/test/verify_picture_test";
1222 prepare_directory (dcp_path);
1224 shared_ptr<dcp::PictureAsset> mp;
1226 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1228 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1230 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1232 auto image = black_image (dcp::Size(width, height));
1233 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1234 int const length = three_d ? frame_rate * 2 : frame_rate;
1235 for (int i = 0; i < length; ++i) {
1236 picture_writer->write (j2c.data(), j2c.size());
1238 picture_writer->finalize ();
1240 auto d = make_shared<dcp::DCP>(dcp_path);
1241 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1242 cpl->set_annotation_text ("A Test DCP");
1243 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1244 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1245 cpl->set_main_sound_sample_rate (48000);
1246 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1247 cpl->set_main_picture_active_area(dcp::Size(width, height));
1248 cpl->set_version_number (1);
1250 auto reel = make_shared<dcp::Reel>();
1253 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1255 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1258 reel->add (simple_markers(frame_rate));
1263 d->set_annotation_text("A Test DCP");
1266 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1272 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1274 auto notes = check_picture_size(width, height, frame_rate, three_d);
1275 BOOST_CHECK_EQUAL (notes.size(), 0U);
1281 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1283 auto notes = check_picture_size(width, height, frame_rate, three_d);
1284 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1285 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1286 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1292 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1294 auto notes = check_picture_size(width, height, frame_rate, three_d);
1295 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1296 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1297 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1303 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1305 auto notes = check_picture_size(width, height, frame_rate, three_d);
1306 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1307 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1308 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1312 BOOST_AUTO_TEST_CASE (verify_picture_size)
1314 using namespace boost::filesystem;
1317 check_picture_size_ok (2048, 858, 24, false);
1318 check_picture_size_ok (2048, 858, 25, false);
1319 check_picture_size_ok (2048, 858, 48, false);
1320 check_picture_size_ok (2048, 858, 24, true);
1321 check_picture_size_ok (2048, 858, 25, true);
1322 check_picture_size_ok (2048, 858, 48, true);
1325 check_picture_size_ok (1998, 1080, 24, false);
1326 check_picture_size_ok (1998, 1080, 25, false);
1327 check_picture_size_ok (1998, 1080, 48, false);
1328 check_picture_size_ok (1998, 1080, 24, true);
1329 check_picture_size_ok (1998, 1080, 25, true);
1330 check_picture_size_ok (1998, 1080, 48, true);
1333 check_picture_size_ok (4096, 1716, 24, false);
1336 check_picture_size_ok (3996, 2160, 24, false);
1338 /* Bad frame size */
1339 check_picture_size_bad_frame_size (2050, 858, 24, false);
1340 check_picture_size_bad_frame_size (2048, 658, 25, false);
1341 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1342 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1344 /* Bad 2K frame rate */
1345 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1346 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1347 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1349 /* Bad 4K frame rate */
1350 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1351 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1354 auto notes = check_picture_size(3996, 2160, 24, true);
1355 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1356 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1357 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1363 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")
1366 std::make_shared<dcp::SubtitleString>(
1374 dcp::Time(start_frame, 24, 24),
1375 dcp::Time(end_frame, 24, 24),
1377 dcp::HAlign::CENTER,
1381 dcp::Direction::LTR,
1393 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1395 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1396 prepare_directory (dir);
1398 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1399 for (int i = 0; i < 2048; ++i) {
1400 add_test_subtitle (asset, i * 24, i * 24 + 20);
1402 asset->set_language (dcp::LanguageTag("de-DE"));
1403 asset->write (dir / "subs.mxf");
1404 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1405 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1407 check_verify_result (
1410 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1412 dcp::VerificationNote::Type::BV21_ERROR,
1413 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1415 canonical(dir / "subs.mxf")
1417 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1424 shared_ptr<dcp::SMPTESubtitleAsset>
1425 make_large_subtitle_asset (path font_file)
1427 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1428 dcp::ArrayData big_fake_font(1024 * 1024);
1429 big_fake_font.write (font_file);
1430 for (int i = 0; i < 116; ++i) {
1431 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1439 verify_timed_text_asset_too_large (string name)
1441 auto const dir = path("build/test") / name;
1442 prepare_directory (dir);
1443 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1444 add_test_subtitle (asset, 0, 240);
1445 asset->set_language (dcp::LanguageTag("de-DE"));
1446 asset->write (dir / "subs.mxf");
1448 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1449 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1451 check_verify_result (
1454 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695532"), canonical(dir / "subs.mxf") },
1455 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1456 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1457 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1458 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1463 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1465 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1466 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1470 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1472 path dir = "build/test/verify_missing_subtitle_language";
1473 prepare_directory (dir);
1474 auto dcp = make_simple (dir, 1, 106);
1477 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1478 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1479 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1480 "<ContentTitleText>Content</ContentTitleText>"
1481 "<AnnotationText>Annotation</AnnotationText>"
1482 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1483 "<ReelNumber>1</ReelNumber>"
1484 "<EditRate>24 1</EditRate>"
1485 "<TimeCodeRate>24</TimeCodeRate>"
1486 "<StartTime>00:00:00:00</StartTime>"
1487 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1489 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1490 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1491 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1497 dcp::File xml_file(dir / "subs.xml", "w");
1498 BOOST_REQUIRE (xml_file);
1499 xml_file.write(xml.c_str(), xml.size(), 1);
1501 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1502 subs->write (dir / "subs.mxf");
1504 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1505 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1506 dcp->set_annotation_text("A Test DCP");
1509 check_verify_result (
1512 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1513 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1518 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1520 path path ("build/test/verify_mismatched_subtitle_languages");
1521 auto constexpr reel_length = 192;
1522 auto dcp = make_simple (path, 2, reel_length);
1523 auto cpl = dcp->cpls()[0];
1526 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1527 subs->set_language (dcp::LanguageTag("de-DE"));
1528 subs->add (simple_subtitle());
1529 subs->write (path / "subs1.mxf");
1530 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1531 cpl->reels()[0]->add(reel_subs);
1535 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1536 subs->set_language (dcp::LanguageTag("en-US"));
1537 subs->add (simple_subtitle());
1538 subs->write (path / "subs2.mxf");
1539 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1540 cpl->reels()[1]->add(reel_subs);
1543 dcp->set_annotation_text("A Test DCP");
1546 check_verify_result (
1549 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1550 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1556 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1558 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1559 auto constexpr reel_length = 192;
1560 auto dcp = make_simple (path, 2, reel_length);
1561 auto cpl = dcp->cpls()[0];
1564 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1565 ccaps->set_language (dcp::LanguageTag("de-DE"));
1566 ccaps->add (simple_subtitle());
1567 ccaps->write (path / "subs1.mxf");
1568 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1569 cpl->reels()[0]->add(reel_ccaps);
1573 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1574 ccaps->set_language (dcp::LanguageTag("en-US"));
1575 ccaps->add (simple_subtitle());
1576 ccaps->write (path / "subs2.mxf");
1577 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1578 cpl->reels()[1]->add(reel_ccaps);
1581 dcp->set_annotation_text("A Test DCP");
1584 check_verify_result (
1587 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1588 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1593 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1595 path dir = "build/test/verify_missing_subtitle_start_time";
1596 prepare_directory (dir);
1597 auto dcp = make_simple (dir, 1, 106);
1600 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1601 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1602 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1603 "<ContentTitleText>Content</ContentTitleText>"
1604 "<AnnotationText>Annotation</AnnotationText>"
1605 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1606 "<ReelNumber>1</ReelNumber>"
1607 "<Language>de-DE</Language>"
1608 "<EditRate>24 1</EditRate>"
1609 "<TimeCodeRate>24</TimeCodeRate>"
1610 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1612 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1613 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1614 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1620 dcp::File xml_file(dir / "subs.xml", "w");
1621 BOOST_REQUIRE (xml_file);
1622 xml_file.write(xml.c_str(), xml.size(), 1);
1624 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1625 subs->write (dir / "subs.mxf");
1627 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1628 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1629 dcp->set_annotation_text("A Test DCP");
1632 check_verify_result (
1635 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1636 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1641 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1643 path dir = "build/test/verify_invalid_subtitle_start_time";
1644 prepare_directory (dir);
1645 auto dcp = make_simple (dir, 1, 106);
1648 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1649 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1650 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1651 "<ContentTitleText>Content</ContentTitleText>"
1652 "<AnnotationText>Annotation</AnnotationText>"
1653 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1654 "<ReelNumber>1</ReelNumber>"
1655 "<Language>de-DE</Language>"
1656 "<EditRate>24 1</EditRate>"
1657 "<TimeCodeRate>24</TimeCodeRate>"
1658 "<StartTime>00:00:02:00</StartTime>"
1659 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1661 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1662 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1663 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1669 dcp::File xml_file(dir / "subs.xml", "w");
1670 BOOST_REQUIRE (xml_file);
1671 xml_file.write(xml.c_str(), xml.size(), 1);
1673 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1674 subs->write (dir / "subs.mxf");
1676 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1677 dcp->cpls().front()->reels().front()->add(reel_subs);
1678 dcp->set_annotation_text("A Test DCP");
1681 check_verify_result (
1684 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1685 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1693 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1696 , v_position(v_position_)
1704 dcp::VAlign v_align;
1710 shared_ptr<dcp::CPL>
1711 dcp_with_text (path dir, vector<TestText> subs)
1713 prepare_directory (dir);
1714 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1715 asset->set_start_time (dcp::Time());
1716 for (auto i: subs) {
1717 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1719 asset->set_language (dcp::LanguageTag("de-DE"));
1720 asset->write (dir / "subs.mxf");
1722 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1723 return write_dcp_with_single_asset (dir, reel_asset);
1728 shared_ptr<dcp::CPL>
1729 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1731 prepare_directory (dir);
1732 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1733 asset->set_start_time (dcp::Time());
1734 asset->set_language (dcp::LanguageTag("de-DE"));
1736 auto subs_mxf = dir / "subs.mxf";
1737 asset->write (subs_mxf);
1739 /* The call to write() puts the asset into the DCP correctly but it will have
1740 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1743 ASDCP::TimedText::MXFWriter writer;
1744 ASDCP::WriterInfo writer_info;
1745 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1747 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1748 DCP_ASSERT (c == Kumu::UUID_Length);
1749 ASDCP::TimedText::TimedTextDescriptor descriptor;
1750 descriptor.ContainerDuration = asset->intrinsic_duration();
1751 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1752 DCP_ASSERT (c == Kumu::UUID_Length);
1753 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1754 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1755 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1756 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1759 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1760 return write_dcp_with_single_asset (dir, reel_asset);
1764 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1766 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1767 /* Just too early */
1768 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1769 check_verify_result (
1772 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1773 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1779 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1781 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1782 /* Just late enough */
1783 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1784 check_verify_result ({dir}, {{ 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_on_second_reel)
1790 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1791 prepare_directory (dir);
1793 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1794 asset1->set_start_time (dcp::Time());
1795 /* Just late enough */
1796 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1797 asset1->set_language (dcp::LanguageTag("de-DE"));
1798 asset1->write (dir / "subs1.mxf");
1799 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1800 auto reel1 = make_shared<dcp::Reel>();
1801 reel1->add (reel_asset1);
1802 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1803 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1804 reel1->add (markers1);
1806 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1807 asset2->set_start_time (dcp::Time());
1808 /* This would be too early on first reel but should be OK on the second */
1809 add_test_subtitle (asset2, 3, 4 * 24);
1810 asset2->set_language (dcp::LanguageTag("de-DE"));
1811 asset2->write (dir / "subs2.mxf");
1812 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1813 auto reel2 = make_shared<dcp::Reel>();
1814 reel2->add (reel_asset2);
1815 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1816 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1817 reel2->add (markers2);
1819 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1822 auto dcp = make_shared<dcp::DCP>(dir);
1824 dcp->set_annotation_text("hello");
1827 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1831 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1833 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1834 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1838 { 5 * 24 + 1, 6 * 24 },
1840 check_verify_result (
1843 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1844 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1849 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1851 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1852 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1856 { 5 * 24 + 16, 8 * 24 },
1858 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1862 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1864 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1865 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1866 check_verify_result (
1869 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1870 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1875 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1877 auto const dir = path("build/test/verify_valid_subtitle_duration");
1878 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1879 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1883 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1885 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1886 prepare_directory (dir);
1887 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1888 asset->set_start_time (dcp::Time());
1889 add_test_subtitle (asset, 0, 4 * 24);
1890 asset->set_language (dcp::LanguageTag("de-DE"));
1891 asset->write (dir / "subs.mxf");
1893 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1894 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1895 check_verify_result (
1898 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1899 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1900 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1907 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1909 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1910 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1913 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1914 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1915 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1916 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1918 check_verify_result (
1921 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1922 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1927 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1929 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1930 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1933 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1934 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1935 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1937 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1941 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1943 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1944 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1947 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1948 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1949 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1950 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1952 check_verify_result (
1955 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1956 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1961 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1963 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1964 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1967 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1968 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1969 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1970 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1972 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1976 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1978 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1979 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1982 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1984 check_verify_result (
1987 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1988 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1993 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1995 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1996 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1999 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2001 check_verify_result (
2004 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2005 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2010 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2012 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2013 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2016 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2017 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2018 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2019 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2021 check_verify_result (
2024 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2025 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2030 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2032 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2033 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2036 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2037 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2038 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2040 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2044 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2046 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2047 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2050 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2051 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2052 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2053 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2055 check_verify_result (
2058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2064 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2066 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2067 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2070 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2071 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2072 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2073 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2075 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2079 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2081 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2082 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2085 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2087 check_verify_result (
2090 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2095 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2097 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2098 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2101 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2103 check_verify_result (
2106 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2107 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2112 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2114 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2115 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2118 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2119 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2120 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2122 check_verify_result (
2125 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2130 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2132 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2133 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2136 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2137 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2138 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2140 check_verify_result (
2143 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2144 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2149 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2151 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2152 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2155 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2156 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2157 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2159 check_verify_result (
2162 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2167 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2169 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2170 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2173 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2174 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2175 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2177 check_verify_result (
2180 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2185 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2187 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2188 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2189 check_verify_result (
2192 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2198 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2200 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2201 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2202 check_verify_result (
2205 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2211 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2213 path const dir("build/test/verify_invalid_sound_frame_rate");
2214 prepare_directory (dir);
2216 auto picture = simple_picture (dir, "foo");
2217 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2218 auto reel = make_shared<dcp::Reel>();
2219 reel->add (reel_picture);
2220 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2221 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2222 reel->add (reel_sound);
2223 reel->add (simple_markers());
2224 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2226 auto dcp = make_shared<dcp::DCP>(dir);
2228 dcp->set_annotation_text("hello");
2231 check_verify_result (
2234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2240 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2242 path const dir("build/test/verify_missing_cpl_annotation_text");
2243 auto dcp = make_simple (dir);
2244 dcp->set_annotation_text("A Test DCP");
2247 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2249 auto const cpl = dcp->cpls()[0];
2252 BOOST_REQUIRE (cpl->file());
2253 Editor e(cpl->file().get());
2254 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2257 check_verify_result (
2260 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2261 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2266 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2268 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2269 auto dcp = make_simple (dir);
2270 dcp->set_annotation_text("A Test DCP");
2273 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2274 auto const cpl = dcp->cpls()[0];
2277 BOOST_REQUIRE (cpl->file());
2278 Editor e(cpl->file().get());
2279 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2282 check_verify_result (
2285 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2286 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2291 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2293 path const dir("build/test/verify_mismatched_asset_duration");
2294 prepare_directory (dir);
2295 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2296 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2298 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2299 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2301 auto reel = make_shared<dcp::Reel>(
2302 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2303 make_shared<dcp::ReelSoundAsset>(ms, 0)
2306 reel->add (simple_markers());
2310 dcp->set_annotation_text("A Test DCP");
2313 check_verify_result (
2316 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2317 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2324 shared_ptr<dcp::CPL>
2325 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2327 prepare_directory (dir);
2328 auto dcp = make_shared<dcp::DCP>(dir);
2329 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2331 auto constexpr reel_length = 192;
2333 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2334 subs->set_language (dcp::LanguageTag("de-DE"));
2335 subs->set_start_time (dcp::Time());
2336 subs->add (simple_subtitle());
2337 subs->write (dir / "subs.mxf");
2338 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2340 auto reel1 = make_shared<dcp::Reel>(
2341 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2342 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2346 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2349 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2350 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2351 reel1->add (markers1);
2355 auto reel2 = make_shared<dcp::Reel>(
2356 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2357 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2361 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2364 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2365 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2366 reel2->add (markers2);
2371 dcp->set_annotation_text("A Test DCP");
2378 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2381 path dir ("build/test/missing_main_subtitle_from_some_reels");
2382 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2383 check_verify_result (
2386 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2387 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2393 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2394 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2395 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2399 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2400 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2401 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2407 shared_ptr<dcp::CPL>
2408 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2410 prepare_directory (dir);
2411 auto dcp = make_shared<dcp::DCP>(dir);
2412 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2414 auto constexpr reel_length = 192;
2416 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2417 subs->set_language (dcp::LanguageTag("de-DE"));
2418 subs->set_start_time (dcp::Time());
2419 subs->add (simple_subtitle());
2420 subs->write (dir / "subs.mxf");
2422 auto reel1 = make_shared<dcp::Reel>(
2423 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2424 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2427 for (int i = 0; i < caps_in_reel1; ++i) {
2428 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2431 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2432 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2433 reel1->add (markers1);
2437 auto reel2 = make_shared<dcp::Reel>(
2438 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2439 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2442 for (int i = 0; i < caps_in_reel2; ++i) {
2443 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2446 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2447 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2448 reel2->add (markers2);
2453 dcp->set_annotation_text("A Test DCP");
2460 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2463 path dir ("build/test/mismatched_closed_caption_asset_counts");
2464 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2465 check_verify_result (
2468 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2474 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2475 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2476 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2480 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2481 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2482 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2489 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2491 prepare_directory (dir);
2492 auto dcp = make_shared<dcp::DCP>(dir);
2493 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2495 auto constexpr reel_length = 192;
2497 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2498 subs->set_language (dcp::LanguageTag("de-DE"));
2499 subs->set_start_time (dcp::Time());
2500 subs->add (simple_subtitle());
2501 subs->write (dir / "subs.mxf");
2502 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2505 auto reel = make_shared<dcp::Reel>(
2506 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2507 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2510 reel->add (reel_text);
2512 reel->add (simple_markers(reel_length));
2517 dcp->set_annotation_text("A Test DCP");
2520 check_verify_result (
2523 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2524 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2529 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2531 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2532 "build/test/verify_subtitle_entry_point_must_be_present",
2533 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2534 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2535 asset->unset_entry_point ();
2539 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2540 "build/test/verify_subtitle_entry_point_must_be_zero",
2541 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2542 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2543 asset->set_entry_point (4);
2547 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2548 "build/test/verify_closed_caption_entry_point_must_be_present",
2549 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2550 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2551 asset->unset_entry_point ();
2555 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2556 "build/test/verify_closed_caption_entry_point_must_be_zero",
2557 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2558 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2559 asset->set_entry_point (9);
2565 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2569 path const dir("build/test/verify_missing_hash");
2570 auto dcp = make_simple (dir);
2571 dcp->set_annotation_text("A Test DCP");
2574 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2575 auto const cpl = dcp->cpls()[0];
2576 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2577 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2578 auto asset_id = cpl->reels()[0]->main_picture()->id();
2581 BOOST_REQUIRE (cpl->file());
2582 Editor e(cpl->file().get());
2583 e.delete_first_line_containing("<Hash>");
2586 check_verify_result (
2589 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2590 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2597 verify_markers_test (
2599 vector<pair<dcp::Marker, dcp::Time>> markers,
2600 vector<dcp::VerificationNote> test_notes
2603 auto dcp = make_simple (dir);
2604 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2605 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2606 for (auto const& i: markers) {
2607 markers_asset->set (i.first, i.second);
2609 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2610 dcp->set_annotation_text("A Test DCP");
2613 check_verify_result ({dir}, test_notes);
2617 BOOST_AUTO_TEST_CASE (verify_markers)
2619 verify_markers_test (
2620 "build/test/verify_markers_all_correct",
2622 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2623 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2624 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2625 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2630 verify_markers_test (
2631 "build/test/verify_markers_missing_ffec",
2633 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2634 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2635 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2638 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2641 verify_markers_test (
2642 "build/test/verify_markers_missing_ffmc",
2644 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2645 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2646 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2649 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2652 verify_markers_test (
2653 "build/test/verify_markers_missing_ffoc",
2655 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2656 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2657 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2660 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2663 verify_markers_test (
2664 "build/test/verify_markers_missing_lfoc",
2666 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2667 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2668 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2671 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2674 verify_markers_test (
2675 "build/test/verify_markers_incorrect_ffoc",
2677 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2678 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2679 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2680 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2683 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2686 verify_markers_test (
2687 "build/test/verify_markers_incorrect_lfoc",
2689 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2690 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2691 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2692 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2695 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2700 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2702 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2703 prepare_directory (dir);
2704 auto dcp = make_simple (dir);
2705 auto cpl = dcp->cpls()[0];
2706 cpl->unset_version_number();
2707 dcp->set_annotation_text("A Test DCP");
2710 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2714 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2716 path dir = "build/test/verify_missing_extension_metadata1";
2717 auto dcp = make_simple (dir);
2718 dcp->set_annotation_text("A Test DCP");
2721 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2722 auto cpl = dcp->cpls()[0];
2725 Editor e (cpl->file().get());
2726 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2729 check_verify_result (
2732 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2733 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2738 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2740 path dir = "build/test/verify_missing_extension_metadata2";
2741 auto dcp = make_simple (dir);
2742 dcp->set_annotation_text("A Test DCP");
2745 auto cpl = dcp->cpls()[0];
2748 Editor e (cpl->file().get());
2749 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2752 check_verify_result (
2755 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2756 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2761 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2763 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2764 auto dcp = make_simple (dir);
2765 dcp->set_annotation_text("A Test DCP");
2768 auto const cpl = dcp->cpls()[0];
2771 Editor e (cpl->file().get());
2772 e.replace ("<meta:Name>A", "<meta:NameX>A");
2773 e.replace ("n</meta:Name>", "n</meta:NameX>");
2776 check_verify_result (
2779 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2780 { 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 },
2781 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2786 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2788 path dir = "build/test/verify_invalid_extension_metadata1";
2789 auto dcp = make_simple (dir);
2790 dcp->set_annotation_text("A Test DCP");
2793 auto cpl = dcp->cpls()[0];
2796 Editor e (cpl->file().get());
2797 e.replace ("Application", "Fred");
2800 check_verify_result (
2803 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2804 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2809 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2811 path dir = "build/test/verify_invalid_extension_metadata2";
2812 auto dcp = make_simple (dir);
2813 dcp->set_annotation_text("A Test DCP");
2816 auto cpl = dcp->cpls()[0];
2819 Editor e (cpl->file().get());
2820 e.replace ("DCP Constraints Profile", "Fred");
2823 check_verify_result (
2826 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2832 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2834 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2835 auto dcp = make_simple (dir);
2836 dcp->set_annotation_text("A Test DCP");
2839 auto const cpl = dcp->cpls()[0];
2842 Editor e (cpl->file().get());
2843 e.replace ("<meta:Value>", "<meta:ValueX>");
2844 e.replace ("</meta:Value>", "</meta:ValueX>");
2847 check_verify_result (
2850 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2851 { 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 },
2852 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2857 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2859 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2860 auto dcp = make_simple (dir);
2861 dcp->set_annotation_text("A Test DCP");
2864 auto const cpl = dcp->cpls()[0];
2867 Editor e (cpl->file().get());
2868 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2871 check_verify_result (
2874 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2875 { 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() },
2880 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2882 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2883 auto dcp = make_simple (dir);
2884 dcp->set_annotation_text("A Test DCP");
2887 auto const cpl = dcp->cpls()[0];
2890 Editor e (cpl->file().get());
2891 e.replace ("<meta:Property>", "<meta:PropertyX>");
2892 e.replace ("</meta:Property>", "</meta:PropertyX>");
2895 check_verify_result (
2898 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2899 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2900 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2905 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2907 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2908 auto dcp = make_simple (dir);
2909 dcp->set_annotation_text("A Test DCP");
2912 auto const cpl = dcp->cpls()[0];
2915 Editor e (cpl->file().get());
2916 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2917 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2920 check_verify_result (
2923 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2924 { 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 },
2925 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2931 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2933 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2934 prepare_directory (dir);
2935 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2936 copy_file (i.path(), dir / i.path().filename());
2939 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2940 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2944 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2947 check_verify_result (
2950 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2951 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2952 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2953 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2954 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2955 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2956 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2957 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2962 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2964 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2965 prepare_directory (dir);
2966 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2967 copy_file (i.path(), dir / i.path().filename());
2970 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2971 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2974 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2977 check_verify_result (
2980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2983 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2984 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2991 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2993 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2994 prepare_directory (dir);
2995 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2996 copy_file (i.path(), dir / i.path().filename());
3000 Editor e (dir / dcp_test1_pkl);
3001 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3004 check_verify_result ({dir}, {});
3008 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3010 path dir ("build/test/verify_must_not_be_partially_encrypted");
3011 prepare_directory (dir);
3015 auto signer = make_shared<dcp::CertificateChain>();
3016 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3017 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3018 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3019 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3021 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3025 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3028 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3029 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3030 for (int i = 0; i < 24; ++i) {
3031 writer->write (j2c.data(), j2c.size());
3033 writer->finalize ();
3035 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3037 auto reel = make_shared<dcp::Reel>(
3038 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3039 make_shared<dcp::ReelSoundAsset>(ms, 0)
3042 reel->add (simple_markers());
3046 cpl->set_content_version (
3047 {"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"}
3049 cpl->set_annotation_text ("A Test DCP");
3050 cpl->set_issuer ("OpenDCP 0.0.25");
3051 cpl->set_creator ("OpenDCP 0.0.25");
3052 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3053 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3054 cpl->set_main_sound_sample_rate (48000);
3055 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3056 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3057 cpl->set_version_number (1);
3061 d.set_issuer("OpenDCP 0.0.25");
3062 d.set_creator("OpenDCP 0.0.25");
3063 d.set_issue_date("2012-07-17T04:45:18+00:00");
3064 d.set_annotation_text("A Test DCP");
3065 d.write_xml(signer);
3067 check_verify_result (
3070 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3075 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3077 vector<dcp::VerificationNote> notes;
3078 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"));
3079 auto reader = picture.start_read ();
3080 auto frame = reader->get_frame (0);
3081 verify_j2k (frame, notes);
3082 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3086 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3088 vector<dcp::VerificationNote> notes;
3089 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3090 auto reader = picture.start_read ();
3091 auto frame = reader->get_frame (0);
3092 verify_j2k (frame, notes);
3093 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3097 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3099 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3100 prepare_directory (dir);
3101 auto dcp = make_simple (dir);
3103 vector<dcp::VerificationNote> notes;
3104 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3105 auto reader = picture.start_read ();
3106 auto frame = reader->get_frame (0);
3107 verify_j2k (frame, notes);
3108 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3112 /** Check that ResourceID and the XML ID being different is spotted */
3113 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3115 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3116 prepare_directory (dir);
3118 ASDCP::WriterInfo writer_info;
3119 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3122 auto mxf_id = dcp::make_uuid ();
3123 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3124 BOOST_REQUIRE (c == Kumu::UUID_Length);
3126 auto resource_id = dcp::make_uuid ();
3127 ASDCP::TimedText::TimedTextDescriptor descriptor;
3128 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3129 DCP_ASSERT (c == Kumu::UUID_Length);
3131 auto xml_id = dcp::make_uuid ();
3132 ASDCP::TimedText::MXFWriter writer;
3133 auto subs_mxf = dir / "subs.mxf";
3134 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3135 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3136 writer.WriteTimedTextResource (dcp::String::compose(
3137 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3138 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3139 "<Id>urn:uuid:%1</Id>"
3140 "<ContentTitleText>Content</ContentTitleText>"
3141 "<AnnotationText>Annotation</AnnotationText>"
3142 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3143 "<ReelNumber>1</ReelNumber>"
3144 "<Language>en-US</Language>"
3145 "<EditRate>25 1</EditRate>"
3146 "<TimeCodeRate>25</TimeCodeRate>"
3147 "<StartTime>00:00:00:00</StartTime>"
3149 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3150 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3151 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3160 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3161 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3163 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3165 check_verify_result (
3168 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3169 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3170 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3171 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3176 /** Check that ResourceID and the MXF ID being the same is spotted */
3177 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3179 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3180 prepare_directory (dir);
3182 ASDCP::WriterInfo writer_info;
3183 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3186 auto mxf_id = dcp::make_uuid ();
3187 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3188 BOOST_REQUIRE (c == Kumu::UUID_Length);
3190 auto resource_id = mxf_id;
3191 ASDCP::TimedText::TimedTextDescriptor descriptor;
3192 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3193 DCP_ASSERT (c == Kumu::UUID_Length);
3195 auto xml_id = resource_id;
3196 ASDCP::TimedText::MXFWriter writer;
3197 auto subs_mxf = dir / "subs.mxf";
3198 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3199 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3200 writer.WriteTimedTextResource (dcp::String::compose(
3201 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3202 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3203 "<Id>urn:uuid:%1</Id>"
3204 "<ContentTitleText>Content</ContentTitleText>"
3205 "<AnnotationText>Annotation</AnnotationText>"
3206 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3207 "<ReelNumber>1</ReelNumber>"
3208 "<Language>en-US</Language>"
3209 "<EditRate>25 1</EditRate>"
3210 "<TimeCodeRate>25</TimeCodeRate>"
3211 "<StartTime>00:00:00:00</StartTime>"
3213 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3214 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3215 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3224 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3225 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3227 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3229 check_verify_result (
3232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3233 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3234 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3236 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3241 /** Check a DCP with a 3D asset marked as 2D */
3242 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3244 check_verify_result (
3245 { private_test / "data" / "xm" },
3248 dcp::VerificationNote::Type::WARNING,
3249 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3252 dcp::VerificationNote::Type::BV21_ERROR,
3253 dcp::VerificationNote::Code::INVALID_STANDARD
3260 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3262 path dir = "build/test/verify_unexpected_things_in_main_markers";
3263 prepare_directory (dir);
3264 auto dcp = make_simple (dir, 1, 24);
3265 dcp->set_annotation_text("A Test DCP");
3269 Editor e (find_cpl(dir));
3271 " <IntrinsicDuration>24</IntrinsicDuration>",
3272 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3276 dcp::CPL cpl (find_cpl(dir));
3278 check_verify_result (
3281 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3282 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3283 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3288 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3290 path dir = "build/test/verify_invalid_content_kind";
3291 prepare_directory (dir);
3292 auto dcp = make_simple (dir, 1, 24);
3293 dcp->set_annotation_text("A Test DCP");
3297 Editor e(find_cpl(dir));
3298 e.replace("trailer", "trip");
3301 dcp::CPL cpl (find_cpl(dir));
3303 check_verify_result (
3306 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3307 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3313 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3315 path dir = "build/test/verify_valid_content_kind";
3316 prepare_directory (dir);
3317 auto dcp = make_simple (dir, 1, 24);
3318 dcp->set_annotation_text("A Test DCP");
3322 Editor e(find_cpl(dir));
3323 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3326 dcp::CPL cpl (find_cpl(dir));
3328 check_verify_result (
3331 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3337 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3339 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3340 prepare_directory(dir);
3341 auto dcp = make_simple(dir, 1, 24);
3344 auto constexpr area = "<meta:MainPictureActiveArea>";
3347 Editor e(find_cpl(dir));
3348 e.delete_lines_after(area, 2);
3349 e.insert(area, "<meta:Height>4080</meta:Height>");
3350 e.insert(area, "<meta:Width>1997</meta:Width>");
3353 dcp::PKL pkl(find_pkl(dir));
3354 dcp::CPL cpl(find_cpl(dir));
3356 check_verify_result(
3359 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3360 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3361 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3362 { 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)) },
3367 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3369 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3370 prepare_directory(dir);
3371 auto dcp = make_simple(dir, 1, 24);
3374 auto constexpr area = "<meta:MainPictureActiveArea>";
3377 Editor e(find_cpl(dir));
3378 e.delete_lines_after(area, 2);
3379 e.insert(area, "<meta:Height>5125</meta:Height>");
3380 e.insert(area, "<meta:Width>9900</meta:Width>");
3383 dcp::PKL pkl(find_pkl(dir));
3384 dcp::CPL cpl(find_cpl(dir));
3386 check_verify_result(
3389 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3390 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3391 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3392 { 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)) },
3393 { 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)) },
3398 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3402 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3403 prepare_directory(dir);
3404 auto dcp = make_simple(dir, 1, 24);
3408 Editor e(find_pkl(dir));
3409 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3412 dcp::PKL pkl(find_pkl(dir));
3414 check_verify_result(
3417 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3422 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3426 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3427 prepare_directory(dir);
3428 auto dcp = make_simple(dir, 1, 24);
3432 Editor e(find_asset_map(dir));
3433 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3436 dcp::PKL pkl(find_pkl(dir));
3437 dcp::AssetMap asset_map(find_asset_map(dir));
3439 check_verify_result(
3442 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3443 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3444 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3449 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3451 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3453 dcp::MXFMetadata mxf_meta;
3454 mxf_meta.company_name = "OpenDCP";
3455 mxf_meta.product_name = "OpenDCP";
3456 mxf_meta.product_version = "0.0.25";
3458 auto constexpr sample_rate = 48000;
3459 auto constexpr frames = 240;
3461 boost::filesystem::remove_all(path);
3462 boost::filesystem::create_directories(path);
3463 auto dcp = make_shared<dcp::DCP>(path);
3464 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3465 cpl->set_annotation_text("hello");
3466 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3467 cpl->set_main_sound_sample_rate(sample_rate);
3468 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3469 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3470 cpl->set_version_number(1);
3474 /* Reel with 2 channels of audio */
3476 auto mp = simple_picture(path, "1", frames, {});
3477 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3479 auto reel = make_shared<dcp::Reel>(
3480 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3481 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3484 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3485 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3492 /* Reel with 6 channels of audio */
3494 auto mp = simple_picture(path, "2", frames, {});
3495 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3497 auto reel = make_shared<dcp::Reel>(
3498 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3499 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3502 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3503 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3510 dcp->set_annotation_text("hello");
3513 check_verify_result(
3516 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3521 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3523 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3525 dcp::MXFMetadata mxf_meta;
3526 mxf_meta.company_name = "OpenDCP";
3527 mxf_meta.product_name = "OpenDCP";
3528 mxf_meta.product_version = "0.0.25";
3530 auto constexpr sample_rate = 48000;
3531 auto constexpr frames = 240;
3533 boost::filesystem::remove_all(path);
3534 boost::filesystem::create_directories(path);
3535 auto dcp = make_shared<dcp::DCP>(path);
3536 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3537 cpl->set_annotation_text("hello");
3538 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3539 cpl->set_main_sound_sample_rate(sample_rate);
3540 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3541 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3542 cpl->set_version_number(1);
3544 auto mp = simple_picture(path, "1", frames, {});
3545 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3547 auto reel = make_shared<dcp::Reel>(
3548 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3549 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3552 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3553 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3554 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3560 dcp->set_annotation_text("hello");
3563 check_verify_result(
3566 { 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)) },