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 sacrifical 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), 26 },
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), 19 },
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", true);
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_valid_smpte_subtitles)
811 path const dir("build/test/verify_valid_smpte_subtitles");
812 prepare_directory (dir);
813 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
814 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
815 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
816 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
818 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
822 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
824 using namespace boost::filesystem;
826 path const dir("build/test/verify_invalid_smpte_subtitles");
827 prepare_directory (dir);
828 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
829 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
830 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
831 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
832 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
834 check_verify_result (
837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
839 dcp::VerificationNote::Type::ERROR,
840 dcp::VerificationNote::Code::INVALID_XML,
841 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
845 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
846 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
851 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
853 path const dir("build/test/verify_empty_text_node_in_subtitles");
854 prepare_directory (dir);
855 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
856 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
857 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
858 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
860 check_verify_result (
863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
864 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
871 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
872 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
874 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
875 prepare_directory (dir);
876 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
877 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
878 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
879 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
881 check_verify_result (
884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
889 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
890 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
892 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
893 prepare_directory (dir);
894 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
895 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
896 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
897 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
899 check_verify_result (
902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
908 BOOST_AUTO_TEST_CASE (verify_external_asset)
910 path const ov_dir("build/test/verify_external_asset");
911 prepare_directory (ov_dir);
913 auto image = black_image ();
914 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
915 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
916 dcp_from_frame (frame, ov_dir);
918 dcp::DCP ov (ov_dir);
921 path const vf_dir("build/test/verify_external_asset_vf");
922 prepare_directory (vf_dir);
924 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
925 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
927 check_verify_result (
930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
936 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
938 path const dir("build/test/verify_valid_cpl_metadata");
939 prepare_directory (dir);
941 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
942 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
943 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
945 auto reel = make_shared<dcp::Reel>();
946 reel->add (reel_asset);
948 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
949 reel->add (simple_markers(16 * 24));
951 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
953 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
954 cpl->set_main_sound_sample_rate (48000);
955 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
956 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
957 cpl->set_version_number (1);
961 dcp.set_annotation_text("hello");
967 find_prefix(path dir, string prefix)
969 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
970 return boost::starts_with(p.filename().string(), prefix);
973 BOOST_REQUIRE(iter != directory_iterator());
978 path find_cpl (path dir)
980 return find_prefix(dir, "cpl_");
987 return find_prefix(dir, "pkl_");
992 /* DCP with invalid CompositionMetadataAsset */
993 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
995 using namespace boost::filesystem;
997 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
998 prepare_directory (dir);
1000 auto reel = make_shared<dcp::Reel>();
1001 reel->add (black_picture_asset(dir));
1002 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1004 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1005 cpl->set_main_sound_sample_rate (48000);
1006 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1007 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1008 cpl->set_version_number (1);
1010 reel->add (simple_markers());
1014 dcp.set_annotation_text("hello");
1018 Editor e (find_cpl(dir));
1019 e.replace ("MainSound", "MainSoundX");
1022 check_verify_result (
1025 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1026 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1028 dcp::VerificationNote::Type::ERROR,
1029 dcp::VerificationNote::Code::INVALID_XML,
1030 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1031 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1032 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1033 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1034 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1035 "ExtensionMetadataList?,)'"),
1036 canonical(cpl->file().get()),
1039 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1044 /* DCP with invalid CompositionMetadataAsset */
1045 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1047 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1048 prepare_directory (dir);
1050 auto reel = make_shared<dcp::Reel>();
1051 reel->add (black_picture_asset(dir));
1052 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1054 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1055 cpl->set_main_sound_sample_rate (48000);
1056 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1057 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1061 dcp.set_annotation_text("hello");
1065 Editor e (find_cpl(dir));
1066 e.replace ("meta:Width", "meta:WidthX");
1069 check_verify_result (
1071 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1076 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1078 path const dir("build/test/verify_invalid_language1");
1079 prepare_directory (dir);
1080 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1081 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1082 asset->_language = "wrong-andbad";
1083 asset->write (dir / "subs.mxf");
1084 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1085 reel_asset->_language = "badlang";
1086 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1088 check_verify_result (
1091 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1092 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1093 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1098 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1099 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1101 path const dir("build/test/verify_invalid_language2");
1102 prepare_directory (dir);
1103 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1104 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1105 asset->_language = "wrong-andbad";
1106 asset->write (dir / "subs.mxf");
1107 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1108 reel_asset->_language = "badlang";
1109 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1111 check_verify_result (
1114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1115 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1121 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1122 * the release territory.
1124 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1126 path const dir("build/test/verify_invalid_language3");
1127 prepare_directory (dir);
1129 auto picture = simple_picture (dir, "foo");
1130 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1131 auto reel = make_shared<dcp::Reel>();
1132 reel->add (reel_picture);
1133 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1134 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1135 reel->add (reel_sound);
1136 reel->add (simple_markers());
1138 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1140 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1141 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1142 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1143 cpl->set_main_sound_sample_rate (48000);
1144 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1145 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1146 cpl->set_version_number (1);
1147 cpl->_release_territory = "fred-jim";
1148 auto dcp = make_shared<dcp::DCP>(dir);
1150 dcp->set_annotation_text("hello");
1153 check_verify_result (
1156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1157 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1158 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1159 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1165 vector<dcp::VerificationNote>
1166 check_picture_size (int width, int height, int frame_rate, bool three_d)
1168 using namespace boost::filesystem;
1170 path dcp_path = "build/test/verify_picture_test";
1171 prepare_directory (dcp_path);
1173 shared_ptr<dcp::PictureAsset> mp;
1175 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1177 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1179 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1181 auto image = black_image (dcp::Size(width, height));
1182 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1183 int const length = three_d ? frame_rate * 2 : frame_rate;
1184 for (int i = 0; i < length; ++i) {
1185 picture_writer->write (j2c.data(), j2c.size());
1187 picture_writer->finalize ();
1189 auto d = make_shared<dcp::DCP>(dcp_path);
1190 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1191 cpl->set_annotation_text ("A Test DCP");
1192 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1193 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1194 cpl->set_main_sound_sample_rate (48000);
1195 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1196 cpl->set_main_picture_active_area(dcp::Size(width, height));
1197 cpl->set_version_number (1);
1199 auto reel = make_shared<dcp::Reel>();
1202 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1204 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1207 reel->add (simple_markers(frame_rate));
1212 d->set_annotation_text("A Test DCP");
1215 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1221 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1223 auto notes = check_picture_size(width, height, frame_rate, three_d);
1224 BOOST_CHECK_EQUAL (notes.size(), 0U);
1230 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1232 auto notes = check_picture_size(width, height, frame_rate, three_d);
1233 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1234 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1235 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1241 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1243 auto notes = check_picture_size(width, height, frame_rate, three_d);
1244 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1245 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1246 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1252 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1254 auto notes = check_picture_size(width, height, frame_rate, three_d);
1255 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1256 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1257 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1261 BOOST_AUTO_TEST_CASE (verify_picture_size)
1263 using namespace boost::filesystem;
1266 check_picture_size_ok (2048, 858, 24, false);
1267 check_picture_size_ok (2048, 858, 25, false);
1268 check_picture_size_ok (2048, 858, 48, false);
1269 check_picture_size_ok (2048, 858, 24, true);
1270 check_picture_size_ok (2048, 858, 25, true);
1271 check_picture_size_ok (2048, 858, 48, true);
1274 check_picture_size_ok (1998, 1080, 24, false);
1275 check_picture_size_ok (1998, 1080, 25, false);
1276 check_picture_size_ok (1998, 1080, 48, false);
1277 check_picture_size_ok (1998, 1080, 24, true);
1278 check_picture_size_ok (1998, 1080, 25, true);
1279 check_picture_size_ok (1998, 1080, 48, true);
1282 check_picture_size_ok (4096, 1716, 24, false);
1285 check_picture_size_ok (3996, 2160, 24, false);
1287 /* Bad frame size */
1288 check_picture_size_bad_frame_size (2050, 858, 24, false);
1289 check_picture_size_bad_frame_size (2048, 658, 25, false);
1290 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1291 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1293 /* Bad 2K frame rate */
1294 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1295 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1296 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1298 /* Bad 4K frame rate */
1299 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1300 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1303 auto notes = check_picture_size(3996, 2160, 24, true);
1304 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1305 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1306 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1312 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")
1315 std::make_shared<dcp::SubtitleString>(
1323 dcp::Time(start_frame, 24, 24),
1324 dcp::Time(end_frame, 24, 24),
1326 dcp::HAlign::CENTER,
1330 dcp::Direction::LTR,
1342 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1344 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1345 prepare_directory (dir);
1347 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1348 for (int i = 0; i < 2048; ++i) {
1349 add_test_subtitle (asset, i * 24, i * 24 + 20);
1351 asset->set_language (dcp::LanguageTag("de-DE"));
1352 asset->write (dir / "subs.mxf");
1353 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1354 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1356 check_verify_result (
1359 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1361 dcp::VerificationNote::Type::BV21_ERROR,
1362 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1364 canonical(dir / "subs.mxf")
1366 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1367 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1373 shared_ptr<dcp::SMPTESubtitleAsset>
1374 make_large_subtitle_asset (path font_file)
1376 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1377 dcp::ArrayData big_fake_font(1024 * 1024);
1378 big_fake_font.write (font_file);
1379 for (int i = 0; i < 116; ++i) {
1380 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1388 verify_timed_text_asset_too_large (string name)
1390 auto const dir = path("build/test") / name;
1391 prepare_directory (dir);
1392 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1393 add_test_subtitle (asset, 0, 240);
1394 asset->set_language (dcp::LanguageTag("de-DE"));
1395 asset->write (dir / "subs.mxf");
1397 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1398 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1400 check_verify_result (
1403 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1404 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1405 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1406 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1407 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1412 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1414 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1415 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1419 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1421 path dir = "build/test/verify_missing_subtitle_language";
1422 prepare_directory (dir);
1423 auto dcp = make_simple (dir, 1, 106);
1426 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1427 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1428 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1429 "<ContentTitleText>Content</ContentTitleText>"
1430 "<AnnotationText>Annotation</AnnotationText>"
1431 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1432 "<ReelNumber>1</ReelNumber>"
1433 "<EditRate>24 1</EditRate>"
1434 "<TimeCodeRate>24</TimeCodeRate>"
1435 "<StartTime>00:00:00:00</StartTime>"
1436 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1438 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1439 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1440 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1446 dcp::File xml_file(dir / "subs.xml", "w");
1447 BOOST_REQUIRE (xml_file);
1448 xml_file.write(xml.c_str(), xml.size(), 1);
1450 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1451 subs->write (dir / "subs.mxf");
1453 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1454 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1455 dcp->set_annotation_text("A Test DCP");
1458 check_verify_result (
1461 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1462 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1467 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1469 path path ("build/test/verify_mismatched_subtitle_languages");
1470 auto constexpr reel_length = 192;
1471 auto dcp = make_simple (path, 2, reel_length);
1472 auto cpl = dcp->cpls()[0];
1475 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1476 subs->set_language (dcp::LanguageTag("de-DE"));
1477 subs->add (simple_subtitle());
1478 subs->write (path / "subs1.mxf");
1479 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1480 cpl->reels()[0]->add(reel_subs);
1484 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1485 subs->set_language (dcp::LanguageTag("en-US"));
1486 subs->add (simple_subtitle());
1487 subs->write (path / "subs2.mxf");
1488 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1489 cpl->reels()[1]->add(reel_subs);
1492 dcp->set_annotation_text("A Test DCP");
1495 check_verify_result (
1498 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1499 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1500 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1505 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1507 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1508 auto constexpr reel_length = 192;
1509 auto dcp = make_simple (path, 2, reel_length);
1510 auto cpl = dcp->cpls()[0];
1513 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1514 ccaps->set_language (dcp::LanguageTag("de-DE"));
1515 ccaps->add (simple_subtitle());
1516 ccaps->write (path / "subs1.mxf");
1517 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1518 cpl->reels()[0]->add(reel_ccaps);
1522 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1523 ccaps->set_language (dcp::LanguageTag("en-US"));
1524 ccaps->add (simple_subtitle());
1525 ccaps->write (path / "subs2.mxf");
1526 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1527 cpl->reels()[1]->add(reel_ccaps);
1530 dcp->set_annotation_text("A Test DCP");
1533 check_verify_result (
1536 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1537 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1542 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1544 path dir = "build/test/verify_missing_subtitle_start_time";
1545 prepare_directory (dir);
1546 auto dcp = make_simple (dir, 1, 106);
1549 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1550 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1551 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1552 "<ContentTitleText>Content</ContentTitleText>"
1553 "<AnnotationText>Annotation</AnnotationText>"
1554 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1555 "<ReelNumber>1</ReelNumber>"
1556 "<Language>de-DE</Language>"
1557 "<EditRate>24 1</EditRate>"
1558 "<TimeCodeRate>24</TimeCodeRate>"
1559 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1561 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1562 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1563 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1569 dcp::File xml_file(dir / "subs.xml", "w");
1570 BOOST_REQUIRE (xml_file);
1571 xml_file.write(xml.c_str(), xml.size(), 1);
1573 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1574 subs->write (dir / "subs.mxf");
1576 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1577 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1578 dcp->set_annotation_text("A Test DCP");
1581 check_verify_result (
1584 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1585 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1590 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1592 path dir = "build/test/verify_invalid_subtitle_start_time";
1593 prepare_directory (dir);
1594 auto dcp = make_simple (dir, 1, 106);
1597 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1598 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1599 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1600 "<ContentTitleText>Content</ContentTitleText>"
1601 "<AnnotationText>Annotation</AnnotationText>"
1602 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1603 "<ReelNumber>1</ReelNumber>"
1604 "<Language>de-DE</Language>"
1605 "<EditRate>24 1</EditRate>"
1606 "<TimeCodeRate>24</TimeCodeRate>"
1607 "<StartTime>00:00:02:00</StartTime>"
1608 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1610 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1611 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1612 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1618 dcp::File xml_file(dir / "subs.xml", "w");
1619 BOOST_REQUIRE (xml_file);
1620 xml_file.write(xml.c_str(), xml.size(), 1);
1622 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1623 subs->write (dir / "subs.mxf");
1625 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1626 dcp->cpls().front()->reels().front()->add(reel_subs);
1627 dcp->set_annotation_text("A Test DCP");
1630 check_verify_result (
1633 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1634 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1642 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1645 , v_position(v_position_)
1653 dcp::VAlign v_align;
1659 shared_ptr<dcp::CPL>
1660 dcp_with_text (path dir, vector<TestText> subs)
1662 prepare_directory (dir);
1663 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1664 asset->set_start_time (dcp::Time());
1665 for (auto i: subs) {
1666 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1668 asset->set_language (dcp::LanguageTag("de-DE"));
1669 asset->write (dir / "subs.mxf");
1671 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1672 return write_dcp_with_single_asset (dir, reel_asset);
1677 shared_ptr<dcp::CPL>
1678 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1680 prepare_directory (dir);
1681 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1682 asset->set_start_time (dcp::Time());
1683 asset->set_language (dcp::LanguageTag("de-DE"));
1685 auto subs_mxf = dir / "subs.mxf";
1686 asset->write (subs_mxf);
1688 /* The call to write() puts the asset into the DCP correctly but it will have
1689 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1692 ASDCP::TimedText::MXFWriter writer;
1693 ASDCP::WriterInfo writer_info;
1694 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1696 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1697 DCP_ASSERT (c == Kumu::UUID_Length);
1698 ASDCP::TimedText::TimedTextDescriptor descriptor;
1699 descriptor.ContainerDuration = asset->intrinsic_duration();
1700 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1701 DCP_ASSERT (c == Kumu::UUID_Length);
1702 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1703 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1704 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1705 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1708 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1709 return write_dcp_with_single_asset (dir, reel_asset);
1713 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1715 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1716 /* Just too early */
1717 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1718 check_verify_result (
1721 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1728 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1730 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1731 /* Just late enough */
1732 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1733 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1737 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1739 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1740 prepare_directory (dir);
1742 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1743 asset1->set_start_time (dcp::Time());
1744 /* Just late enough */
1745 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1746 asset1->set_language (dcp::LanguageTag("de-DE"));
1747 asset1->write (dir / "subs1.mxf");
1748 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1749 auto reel1 = make_shared<dcp::Reel>();
1750 reel1->add (reel_asset1);
1751 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1752 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1753 reel1->add (markers1);
1755 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1756 asset2->set_start_time (dcp::Time());
1757 /* This would be too early on first reel but should be OK on the second */
1758 add_test_subtitle (asset2, 3, 4 * 24);
1759 asset2->set_language (dcp::LanguageTag("de-DE"));
1760 asset2->write (dir / "subs2.mxf");
1761 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1762 auto reel2 = make_shared<dcp::Reel>();
1763 reel2->add (reel_asset2);
1764 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1765 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1766 reel2->add (markers2);
1768 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1771 auto dcp = make_shared<dcp::DCP>(dir);
1773 dcp->set_annotation_text("hello");
1776 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1780 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1782 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1783 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1787 { 5 * 24 + 1, 6 * 24 },
1789 check_verify_result (
1792 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1798 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1800 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1801 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1805 { 5 * 24 + 16, 8 * 24 },
1807 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1811 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1813 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1814 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1815 check_verify_result (
1818 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1824 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1826 auto const dir = path("build/test/verify_valid_subtitle_duration");
1827 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1828 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1832 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1834 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1835 prepare_directory (dir);
1836 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1837 asset->set_start_time (dcp::Time());
1838 add_test_subtitle (asset, 0, 4 * 24);
1839 asset->set_language (dcp::LanguageTag("de-DE"));
1840 asset->write (dir / "subs.mxf");
1842 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1843 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1844 check_verify_result (
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1848 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1849 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1856 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1858 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1859 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1862 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1863 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1864 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1865 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1867 check_verify_result (
1870 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1871 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1876 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1878 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1879 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1882 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1883 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1884 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1886 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1890 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1892 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1893 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1896 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1897 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1898 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1899 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1901 check_verify_result (
1904 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1905 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1910 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1912 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1913 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1916 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1917 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1918 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1919 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1921 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1925 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1927 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1928 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1931 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1933 check_verify_result (
1936 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1942 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1944 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1945 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1948 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1950 check_verify_result (
1953 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1954 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1959 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1961 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1962 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1965 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1966 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1967 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1968 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1970 check_verify_result (
1973 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1974 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1979 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1981 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1982 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1985 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1986 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1987 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1989 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1993 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1995 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1996 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1999 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2000 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2001 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2002 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2004 check_verify_result (
2007 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2008 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2013 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2015 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2016 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2019 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2020 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2021 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2022 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2024 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2028 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2030 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2031 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2034 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2036 check_verify_result (
2039 { 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_length)
2046 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2047 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2050 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2052 check_verify_result (
2055 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2056 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2061 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2063 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2064 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2067 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2068 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2069 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2071 check_verify_result (
2074 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2079 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2081 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2082 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2085 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2086 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2087 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2089 check_verify_result (
2092 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2093 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2098 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2100 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2101 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2104 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2105 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2106 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2108 check_verify_result (
2111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2116 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2118 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2119 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2122 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2123 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2124 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2126 check_verify_result (
2129 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2134 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2136 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2137 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2138 check_verify_result (
2141 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2147 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2149 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2150 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2151 check_verify_result (
2154 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2160 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2162 path const dir("build/test/verify_invalid_sound_frame_rate");
2163 prepare_directory (dir);
2165 auto picture = simple_picture (dir, "foo");
2166 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2167 auto reel = make_shared<dcp::Reel>();
2168 reel->add (reel_picture);
2169 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2170 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2171 reel->add (reel_sound);
2172 reel->add (simple_markers());
2173 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2175 auto dcp = make_shared<dcp::DCP>(dir);
2177 dcp->set_annotation_text("hello");
2180 check_verify_result (
2183 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2189 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2191 path const dir("build/test/verify_missing_cpl_annotation_text");
2192 auto dcp = make_simple (dir);
2193 dcp->set_annotation_text("A Test DCP");
2196 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2198 auto const cpl = dcp->cpls()[0];
2201 BOOST_REQUIRE (cpl->file());
2202 Editor e(cpl->file().get());
2203 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2206 check_verify_result (
2209 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2210 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2215 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2217 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2218 auto dcp = make_simple (dir);
2219 dcp->set_annotation_text("A Test DCP");
2222 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2223 auto const cpl = dcp->cpls()[0];
2226 BOOST_REQUIRE (cpl->file());
2227 Editor e(cpl->file().get());
2228 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2231 check_verify_result (
2234 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2235 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2240 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2242 path const dir("build/test/verify_mismatched_asset_duration");
2243 prepare_directory (dir);
2244 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2245 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2247 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2248 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2250 auto reel = make_shared<dcp::Reel>(
2251 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2252 make_shared<dcp::ReelSoundAsset>(ms, 0)
2255 reel->add (simple_markers());
2259 dcp->set_annotation_text("A Test DCP");
2262 check_verify_result (
2265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2266 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2273 shared_ptr<dcp::CPL>
2274 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2276 prepare_directory (dir);
2277 auto dcp = make_shared<dcp::DCP>(dir);
2278 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2280 auto constexpr reel_length = 192;
2282 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2283 subs->set_language (dcp::LanguageTag("de-DE"));
2284 subs->set_start_time (dcp::Time());
2285 subs->add (simple_subtitle());
2286 subs->write (dir / "subs.mxf");
2287 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2289 auto reel1 = make_shared<dcp::Reel>(
2290 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2291 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2295 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2298 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2299 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2300 reel1->add (markers1);
2304 auto reel2 = make_shared<dcp::Reel>(
2305 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2306 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2310 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2313 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2314 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2315 reel2->add (markers2);
2320 dcp->set_annotation_text("A Test DCP");
2327 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2330 path dir ("build/test/missing_main_subtitle_from_some_reels");
2331 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2332 check_verify_result (
2335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2342 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2343 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2344 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2348 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2349 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2350 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2356 shared_ptr<dcp::CPL>
2357 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2359 prepare_directory (dir);
2360 auto dcp = make_shared<dcp::DCP>(dir);
2361 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2363 auto constexpr reel_length = 192;
2365 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2366 subs->set_language (dcp::LanguageTag("de-DE"));
2367 subs->set_start_time (dcp::Time());
2368 subs->add (simple_subtitle());
2369 subs->write (dir / "subs.mxf");
2371 auto reel1 = make_shared<dcp::Reel>(
2372 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2373 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2376 for (int i = 0; i < caps_in_reel1; ++i) {
2377 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2380 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2381 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2382 reel1->add (markers1);
2386 auto reel2 = make_shared<dcp::Reel>(
2387 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2388 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2391 for (int i = 0; i < caps_in_reel2; ++i) {
2392 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2395 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2396 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2397 reel2->add (markers2);
2402 dcp->set_annotation_text("A Test DCP");
2409 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2412 path dir ("build/test/mismatched_closed_caption_asset_counts");
2413 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2414 check_verify_result (
2417 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2423 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2424 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2425 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2429 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2430 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2431 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2438 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2440 prepare_directory (dir);
2441 auto dcp = make_shared<dcp::DCP>(dir);
2442 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2444 auto constexpr reel_length = 192;
2446 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2447 subs->set_language (dcp::LanguageTag("de-DE"));
2448 subs->set_start_time (dcp::Time());
2449 subs->add (simple_subtitle());
2450 subs->write (dir / "subs.mxf");
2451 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2454 auto reel = make_shared<dcp::Reel>(
2455 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2456 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2459 reel->add (reel_text);
2461 reel->add (simple_markers(reel_length));
2466 dcp->set_annotation_text("A Test DCP");
2469 check_verify_result (
2472 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2473 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2478 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2480 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2481 "build/test/verify_subtitle_entry_point_must_be_present",
2482 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2483 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2484 asset->unset_entry_point ();
2488 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2489 "build/test/verify_subtitle_entry_point_must_be_zero",
2490 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2491 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2492 asset->set_entry_point (4);
2496 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2497 "build/test/verify_closed_caption_entry_point_must_be_present",
2498 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2499 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2500 asset->unset_entry_point ();
2504 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2505 "build/test/verify_closed_caption_entry_point_must_be_zero",
2506 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2507 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2508 asset->set_entry_point (9);
2514 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2518 path const dir("build/test/verify_missing_hash");
2519 auto dcp = make_simple (dir);
2520 dcp->set_annotation_text("A Test DCP");
2523 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2524 auto const cpl = dcp->cpls()[0];
2525 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2526 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2527 auto asset_id = cpl->reels()[0]->main_picture()->id();
2530 BOOST_REQUIRE (cpl->file());
2531 Editor e(cpl->file().get());
2532 e.delete_first_line_containing("<Hash>");
2535 check_verify_result (
2538 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2539 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2546 verify_markers_test (
2548 vector<pair<dcp::Marker, dcp::Time>> markers,
2549 vector<dcp::VerificationNote> test_notes
2552 auto dcp = make_simple (dir);
2553 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2554 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2555 for (auto const& i: markers) {
2556 markers_asset->set (i.first, i.second);
2558 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2559 dcp->set_annotation_text("A Test DCP");
2562 check_verify_result ({dir}, test_notes);
2566 BOOST_AUTO_TEST_CASE (verify_markers)
2568 verify_markers_test (
2569 "build/test/verify_markers_all_correct",
2571 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2572 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2573 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2574 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2579 verify_markers_test (
2580 "build/test/verify_markers_missing_ffec",
2582 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2583 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2584 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2587 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2590 verify_markers_test (
2591 "build/test/verify_markers_missing_ffmc",
2593 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2594 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2595 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2598 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2601 verify_markers_test (
2602 "build/test/verify_markers_missing_ffoc",
2604 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2605 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2606 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2609 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2612 verify_markers_test (
2613 "build/test/verify_markers_missing_lfoc",
2615 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2616 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2617 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2620 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2623 verify_markers_test (
2624 "build/test/verify_markers_incorrect_ffoc",
2626 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2627 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2628 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2629 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2632 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2635 verify_markers_test (
2636 "build/test/verify_markers_incorrect_lfoc",
2638 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2639 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2640 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2641 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2644 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2649 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2651 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2652 prepare_directory (dir);
2653 auto dcp = make_simple (dir);
2654 auto cpl = dcp->cpls()[0];
2655 cpl->unset_version_number();
2656 dcp->set_annotation_text("A Test DCP");
2659 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2663 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2665 path dir = "build/test/verify_missing_extension_metadata1";
2666 auto dcp = make_simple (dir);
2667 dcp->set_annotation_text("A Test DCP");
2670 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2671 auto cpl = dcp->cpls()[0];
2674 Editor e (cpl->file().get());
2675 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2678 check_verify_result (
2681 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2682 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2687 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2689 path dir = "build/test/verify_missing_extension_metadata2";
2690 auto dcp = make_simple (dir);
2691 dcp->set_annotation_text("A Test DCP");
2694 auto cpl = dcp->cpls()[0];
2697 Editor e (cpl->file().get());
2698 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2701 check_verify_result (
2704 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2710 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2712 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2713 auto dcp = make_simple (dir);
2714 dcp->set_annotation_text("A Test DCP");
2717 auto const cpl = dcp->cpls()[0];
2720 Editor e (cpl->file().get());
2721 e.replace ("<meta:Name>A", "<meta:NameX>A");
2722 e.replace ("n</meta:Name>", "n</meta:NameX>");
2725 check_verify_result (
2728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2729 { 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 },
2730 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2735 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2737 path dir = "build/test/verify_invalid_extension_metadata1";
2738 auto dcp = make_simple (dir);
2739 dcp->set_annotation_text("A Test DCP");
2742 auto cpl = dcp->cpls()[0];
2745 Editor e (cpl->file().get());
2746 e.replace ("Application", "Fred");
2749 check_verify_result (
2752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2753 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2758 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2760 path dir = "build/test/verify_invalid_extension_metadata2";
2761 auto dcp = make_simple (dir);
2762 dcp->set_annotation_text("A Test DCP");
2765 auto cpl = dcp->cpls()[0];
2768 Editor e (cpl->file().get());
2769 e.replace ("DCP Constraints Profile", "Fred");
2772 check_verify_result (
2775 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2781 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2783 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2784 auto dcp = make_simple (dir);
2785 dcp->set_annotation_text("A Test DCP");
2788 auto const cpl = dcp->cpls()[0];
2791 Editor e (cpl->file().get());
2792 e.replace ("<meta:Value>", "<meta:ValueX>");
2793 e.replace ("</meta:Value>", "</meta:ValueX>");
2796 check_verify_result (
2799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2800 { 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 },
2801 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2806 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2808 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2809 auto dcp = make_simple (dir);
2810 dcp->set_annotation_text("A Test DCP");
2813 auto const cpl = dcp->cpls()[0];
2816 Editor e (cpl->file().get());
2817 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2820 check_verify_result (
2823 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2824 { 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() },
2829 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2831 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2832 auto dcp = make_simple (dir);
2833 dcp->set_annotation_text("A Test DCP");
2836 auto const cpl = dcp->cpls()[0];
2839 Editor e (cpl->file().get());
2840 e.replace ("<meta:Property>", "<meta:PropertyX>");
2841 e.replace ("</meta:Property>", "</meta:PropertyX>");
2844 check_verify_result (
2847 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2849 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2854 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2856 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2857 auto dcp = make_simple (dir);
2858 dcp->set_annotation_text("A Test DCP");
2861 auto const cpl = dcp->cpls()[0];
2864 Editor e (cpl->file().get());
2865 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2866 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2869 check_verify_result (
2872 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2873 { 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 },
2874 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2880 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2882 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2883 prepare_directory (dir);
2884 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2885 copy_file (i.path(), dir / i.path().filename());
2888 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2889 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2893 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2896 check_verify_result (
2899 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2904 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2905 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2906 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2911 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2913 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2914 prepare_directory (dir);
2915 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2916 copy_file (i.path(), dir / i.path().filename());
2919 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2920 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2923 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2926 check_verify_result (
2929 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2932 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2933 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2934 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2940 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2942 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2943 prepare_directory (dir);
2944 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2945 copy_file (i.path(), dir / i.path().filename());
2949 Editor e (dir / dcp_test1_pkl);
2950 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2953 check_verify_result ({dir}, {});
2957 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2959 path dir ("build/test/verify_must_not_be_partially_encrypted");
2960 prepare_directory (dir);
2964 auto signer = make_shared<dcp::CertificateChain>();
2965 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2966 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2967 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2968 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2970 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2974 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2977 auto writer = mp->start_write (dir / "video.mxf", false);
2978 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2979 for (int i = 0; i < 24; ++i) {
2980 writer->write (j2c.data(), j2c.size());
2982 writer->finalize ();
2984 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2986 auto reel = make_shared<dcp::Reel>(
2987 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2988 make_shared<dcp::ReelSoundAsset>(ms, 0)
2991 reel->add (simple_markers());
2995 cpl->set_content_version (
2996 {"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"}
2998 cpl->set_annotation_text ("A Test DCP");
2999 cpl->set_issuer ("OpenDCP 0.0.25");
3000 cpl->set_creator ("OpenDCP 0.0.25");
3001 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3002 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3003 cpl->set_main_sound_sample_rate (48000);
3004 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3005 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3006 cpl->set_version_number (1);
3010 d.set_issuer("OpenDCP 0.0.25");
3011 d.set_creator("OpenDCP 0.0.25");
3012 d.set_issue_date("2012-07-17T04:45:18+00:00");
3013 d.set_annotation_text("A Test DCP");
3014 d.write_xml(signer);
3016 check_verify_result (
3019 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3024 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3026 vector<dcp::VerificationNote> notes;
3027 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"));
3028 auto reader = picture.start_read ();
3029 auto frame = reader->get_frame (0);
3030 verify_j2k (frame, notes);
3031 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3035 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3037 vector<dcp::VerificationNote> notes;
3038 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3039 auto reader = picture.start_read ();
3040 auto frame = reader->get_frame (0);
3041 verify_j2k (frame, notes);
3042 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3046 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3048 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3049 prepare_directory (dir);
3050 auto dcp = make_simple (dir);
3052 vector<dcp::VerificationNote> notes;
3053 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3054 auto reader = picture.start_read ();
3055 auto frame = reader->get_frame (0);
3056 verify_j2k (frame, notes);
3057 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3061 /** Check that ResourceID and the XML ID being different is spotted */
3062 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3064 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3065 prepare_directory (dir);
3067 ASDCP::WriterInfo writer_info;
3068 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3071 auto mxf_id = dcp::make_uuid ();
3072 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3073 BOOST_REQUIRE (c == Kumu::UUID_Length);
3075 auto resource_id = dcp::make_uuid ();
3076 ASDCP::TimedText::TimedTextDescriptor descriptor;
3077 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3078 DCP_ASSERT (c == Kumu::UUID_Length);
3080 auto xml_id = dcp::make_uuid ();
3081 ASDCP::TimedText::MXFWriter writer;
3082 auto subs_mxf = dir / "subs.mxf";
3083 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3084 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3085 writer.WriteTimedTextResource (dcp::String::compose(
3086 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3087 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3088 "<Id>urn:uuid:%1</Id>"
3089 "<ContentTitleText>Content</ContentTitleText>"
3090 "<AnnotationText>Annotation</AnnotationText>"
3091 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3092 "<ReelNumber>1</ReelNumber>"
3093 "<Language>en-US</Language>"
3094 "<EditRate>25 1</EditRate>"
3095 "<TimeCodeRate>25</TimeCodeRate>"
3096 "<StartTime>00:00:00:00</StartTime>"
3098 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3099 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3100 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3109 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3110 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3112 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3114 check_verify_result (
3117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3119 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3120 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3125 /** Check that ResourceID and the MXF ID being the same is spotted */
3126 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3128 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3129 prepare_directory (dir);
3131 ASDCP::WriterInfo writer_info;
3132 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3135 auto mxf_id = dcp::make_uuid ();
3136 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3137 BOOST_REQUIRE (c == Kumu::UUID_Length);
3139 auto resource_id = mxf_id;
3140 ASDCP::TimedText::TimedTextDescriptor descriptor;
3141 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3142 DCP_ASSERT (c == Kumu::UUID_Length);
3144 auto xml_id = resource_id;
3145 ASDCP::TimedText::MXFWriter writer;
3146 auto subs_mxf = dir / "subs.mxf";
3147 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3148 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3149 writer.WriteTimedTextResource (dcp::String::compose(
3150 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3151 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3152 "<Id>urn:uuid:%1</Id>"
3153 "<ContentTitleText>Content</ContentTitleText>"
3154 "<AnnotationText>Annotation</AnnotationText>"
3155 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3156 "<ReelNumber>1</ReelNumber>"
3157 "<Language>en-US</Language>"
3158 "<EditRate>25 1</EditRate>"
3159 "<TimeCodeRate>25</TimeCodeRate>"
3160 "<StartTime>00:00:00:00</StartTime>"
3162 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3163 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3164 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3173 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3174 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3176 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3178 check_verify_result (
3181 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3182 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3183 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3189 /** Check a DCP with a 3D asset marked as 2D */
3190 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3192 check_verify_result (
3193 { private_test / "data" / "xm" },
3196 dcp::VerificationNote::Type::WARNING,
3197 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3200 dcp::VerificationNote::Type::BV21_ERROR,
3201 dcp::VerificationNote::Code::INVALID_STANDARD
3208 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3210 path dir = "build/test/verify_unexpected_things_in_main_markers";
3211 prepare_directory (dir);
3212 auto dcp = make_simple (dir, 1, 24);
3213 dcp->set_annotation_text("A Test DCP");
3217 Editor e (find_cpl(dir));
3219 " <IntrinsicDuration>24</IntrinsicDuration>",
3220 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3224 dcp::CPL cpl (find_cpl(dir));
3226 check_verify_result (
3229 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3230 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3231 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3236 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3238 path dir = "build/test/verify_invalid_content_kind";
3239 prepare_directory (dir);
3240 auto dcp = make_simple (dir, 1, 24);
3241 dcp->set_annotation_text("A Test DCP");
3245 Editor e(find_cpl(dir));
3246 e.replace("trailer", "trip");
3249 dcp::CPL cpl (find_cpl(dir));
3251 check_verify_result (
3254 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3255 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3261 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3263 path dir = "build/test/verify_valid_content_kind";
3264 prepare_directory (dir);
3265 auto dcp = make_simple (dir, 1, 24);
3266 dcp->set_annotation_text("A Test DCP");
3270 Editor e(find_cpl(dir));
3271 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3274 dcp::CPL cpl (find_cpl(dir));
3276 check_verify_result (
3279 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3285 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3287 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3288 prepare_directory(dir);
3289 auto dcp = make_simple(dir, 1, 24);
3292 auto constexpr area = "<meta:MainPictureActiveArea>";
3295 Editor e(find_cpl(dir));
3296 e.delete_lines_after(area, 2);
3297 e.insert(area, "<meta:Height>4080</meta:Height>");
3298 e.insert(area, "<meta:Width>1997</meta:Width>");
3301 dcp::PKL pkl(find_pkl(dir));
3302 dcp::CPL cpl(find_cpl(dir));
3304 check_verify_result(
3307 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3308 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3309 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3310 { 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)) },
3315 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3317 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3318 prepare_directory(dir);
3319 auto dcp = make_simple(dir, 1, 24);
3322 auto constexpr area = "<meta:MainPictureActiveArea>";
3325 Editor e(find_cpl(dir));
3326 e.delete_lines_after(area, 2);
3327 e.insert(area, "<meta:Height>5125</meta:Height>");
3328 e.insert(area, "<meta:Width>9900</meta:Width>");
3331 dcp::PKL pkl(find_pkl(dir));
3332 dcp::CPL cpl(find_cpl(dir));
3334 check_verify_result(
3337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3340 { 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)) },
3341 { 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)) },