2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/algorithm/string.hpp>
62 #include <boost/random.hpp>
63 #include <boost/test/unit_test.hpp>
70 using std::make_shared;
72 using std::shared_ptr;
75 using boost::optional;
76 using namespace boost::filesystem;
79 static list<pair<string, optional<path>>> stages;
81 static string filename_to_id(boost::filesystem::path path)
83 return path.string().substr(4, path.string().length() - 8);
86 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
87 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
89 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
90 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
92 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
94 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
95 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
97 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
98 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
101 stage (string s, optional<path> p)
103 stages.push_back (make_pair (s, p));
113 prepare_directory (path path)
115 using namespace boost::filesystem;
117 create_directories (path);
121 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
122 * to make a new sacrificial test DCP.
125 setup (int reference_number, string verify_test_suffix)
127 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
128 prepare_directory (dir);
129 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
130 copy_file (i.path(), dir / i.path().filename());
139 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
141 auto reel = make_shared<dcp::Reel>();
142 reel->add (reel_asset);
143 reel->add (simple_markers());
145 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
147 auto dcp = make_shared<dcp::DCP>(dir);
149 dcp->set_annotation_text("hello");
156 /** Class that can alter a file by searching and replacing strings within it.
157 * On destruction modifies the file whose name was given to the constructor.
165 _content = dcp::file_to_string (_path);
170 auto f = fopen(_path.string().c_str(), "w");
172 fwrite (_content.c_str(), _content.length(), 1, f);
179 ChangeChecker(Editor* editor)
182 _old_content = _editor->_content;
187 BOOST_REQUIRE(_old_content != _editor->_content);
191 std::string _old_content;
194 void replace (string a, string b)
196 ChangeChecker cc(this);
197 boost::algorithm::replace_all (_content, a, b);
200 void delete_first_line_containing (string s)
202 ChangeChecker cc(this);
203 auto lines = as_lines();
206 for (auto i: lines) {
207 if (i.find(s) == string::npos || done) {
208 _content += i + "\n";
215 void delete_lines (string from, string to)
217 ChangeChecker cc(this);
218 auto lines = as_lines();
219 bool deleting = false;
221 for (auto i: lines) {
222 if (i.find(from) != string::npos) {
226 _content += i + "\n";
228 if (deleting && i.find(to) != string::npos) {
234 void insert (string after, string line)
236 ChangeChecker cc(this);
237 auto lines = as_lines();
239 bool replaced = false;
240 for (auto i: lines) {
241 _content += i + "\n";
242 if (!replaced && i.find(after) != string::npos) {
243 _content += line + "\n";
249 void delete_lines_after(string after, int lines_to_delete)
251 ChangeChecker cc(this);
252 auto lines = as_lines();
254 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
255 return line.find(after) != string::npos;
258 for (auto i = lines.begin(); i != lines.end(); ++i) {
260 to_delete = lines_to_delete;
261 _content += *i + "\n";
262 } else if (to_delete == 0) {
263 _content += *i + "\n";
271 friend class ChangeChecker;
273 vector<string> as_lines() const
275 vector<string> lines;
276 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
281 std::string _content;
285 LIBDCP_DISABLE_WARNINGS
288 dump_notes (vector<dcp::VerificationNote> const & notes)
290 for (auto i: notes) {
291 std::cout << dcp::note_to_string(i) << "\n";
294 LIBDCP_ENABLE_WARNINGS
299 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
301 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
302 std::sort (notes.begin(), notes.end());
303 std::sort (test_notes.begin(), test_notes.end());
305 string message = "\nVerification notes from test:\n";
306 for (auto i: notes) {
307 message += " " + note_to_string(i) + "\n";
308 message += dcp::String::compose(
309 " [%1 %2 %3 %4 %5]\n",
310 static_cast<int>(i.type()),
311 static_cast<int>(i.code()),
312 i.note().get_value_or("<none>"),
313 i.file().get_value_or("<none>"),
314 i.line().get_value_or(0)
317 message += "Expected:\n";
318 for (auto i: test_notes) {
319 message += " " + note_to_string(i) + "\n";
320 message += dcp::String::compose(
321 " [%1 %2 %3 %4 %5]\n",
322 static_cast<int>(i.type()),
323 static_cast<int>(i.code()),
324 i.note().get_value_or("<none>"),
325 i.file().get_value_or("<none>"),
326 i.line().get_value_or(0)
330 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
334 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
335 * replacing from with to. Verify the resulting DCP and check that the results match the given
340 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
342 auto dir = setup (1, suffix);
345 Editor e (file(suffix));
346 e.replace (from, to);
349 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
351 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
352 auto i = notes.begin();
353 auto j = codes.begin();
354 while (i != notes.end()) {
355 BOOST_CHECK_EQUAL (i->code(), *j);
362 BOOST_AUTO_TEST_CASE (verify_no_error)
365 auto dir = setup (1, "no_error");
366 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
368 path const cpl_file = dir / dcp_test1_cpl;
369 path const pkl_file = dir / dcp_test1_pkl;
370 path const assetmap_file = dir / "ASSETMAP.xml";
372 auto st = stages.begin();
373 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
374 BOOST_REQUIRE (st->second);
375 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
377 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
378 BOOST_REQUIRE (st->second);
379 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
381 BOOST_CHECK_EQUAL (st->first, "Checking reel");
382 BOOST_REQUIRE (!st->second);
384 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
385 BOOST_REQUIRE (st->second);
386 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
388 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
389 BOOST_REQUIRE (st->second);
390 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
392 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
393 BOOST_REQUIRE (st->second);
394 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
396 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
397 BOOST_REQUIRE (st->second);
398 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
400 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
401 BOOST_REQUIRE (st->second);
402 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
404 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
405 BOOST_REQUIRE (st->second);
406 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
408 BOOST_REQUIRE (st == stages.end());
410 BOOST_CHECK_EQUAL (notes.size(), 0U);
414 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
416 using namespace boost::filesystem;
418 auto dir = setup (1, "incorrect_picture_sound_hash");
420 auto video_path = path(dir / "video.mxf");
421 auto mod = fopen(video_path.string().c_str(), "r+b");
423 fseek (mod, 4096, SEEK_SET);
425 fwrite (&x, sizeof(x), 1, mod);
428 auto audio_path = path(dir / "audio.mxf");
429 mod = fopen(audio_path.string().c_str(), "r+b");
431 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
432 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
435 dcp::ASDCPErrorSuspender sus;
436 check_verify_result (
439 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
440 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
445 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
447 using namespace boost::filesystem;
449 auto dir = setup (1, "mismatched_picture_sound_hashes");
452 Editor e (dir / dcp_test1_pkl);
453 e.replace ("<Hash>", "<Hash>x");
456 check_verify_result (
459 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
460 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
461 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
462 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 },
463 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
464 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 },
469 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
471 auto dir = setup (1, "failed_read_content_kind");
474 Editor e (dir / dcp_test1_cpl);
475 e.replace ("<ContentKind>", "<ContentKind>x");
478 check_verify_result (
481 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
482 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
491 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
499 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
505 asset_map (string suffix)
507 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
511 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
513 check_verify_result_after_replace (
514 "invalid_picture_frame_rate", &cpl,
515 "<FrameRate>24 1", "<FrameRate>99 1",
516 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
517 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
521 BOOST_AUTO_TEST_CASE (verify_missing_asset)
523 auto dir = setup (1, "missing_asset");
524 remove (dir / "video.mxf");
525 check_verify_result (
528 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
533 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
535 check_verify_result_after_replace (
536 "empty_asset_path", &asset_map,
537 "<Path>video.mxf</Path>", "<Path></Path>",
538 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
543 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
545 check_verify_result_after_replace (
546 "mismatched_standard", &cpl,
547 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
548 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
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::INVALID_XML,
554 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
559 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
561 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
562 check_verify_result_after_replace (
563 "invalid_xml_cpl_id", &cpl,
564 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
565 { dcp::VerificationNote::Code::INVALID_XML }
570 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
572 check_verify_result_after_replace (
573 "invalid_xml_issue_date", &cpl,
574 "<IssueDate>", "<IssueDate>x",
575 { dcp::VerificationNote::Code::INVALID_XML,
576 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
581 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
583 check_verify_result_after_replace (
584 "invalid_xml_pkl_id", &pkl,
585 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
586 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
587 { dcp::VerificationNote::Code::INVALID_XML }
592 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
594 check_verify_result_after_replace (
595 "invalid_xml_asset_map_id", &asset_map,
596 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
597 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
598 { dcp::VerificationNote::Code::INVALID_XML }
603 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
606 auto dir = setup (3, "verify_invalid_standard");
607 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
609 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
610 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
611 path const assetmap_file = dir / "ASSETMAP";
613 auto st = stages.begin();
614 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
615 BOOST_REQUIRE (st->second);
616 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
618 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
619 BOOST_REQUIRE (st->second);
620 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
622 BOOST_CHECK_EQUAL (st->first, "Checking reel");
623 BOOST_REQUIRE (!st->second);
625 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
626 BOOST_REQUIRE (st->second);
627 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
629 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
630 BOOST_REQUIRE (st->second);
631 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
633 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
634 BOOST_REQUIRE (st->second);
635 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
637 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
638 BOOST_REQUIRE (st->second);
639 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
641 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
642 BOOST_REQUIRE (st->second);
643 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
645 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
646 BOOST_REQUIRE (st->second);
647 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
649 BOOST_REQUIRE (st == stages.end());
651 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
652 auto i = notes.begin ();
653 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
654 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
656 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
657 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
660 /* DCP with a short asset */
661 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
663 auto dir = setup (8, "invalid_duration");
664 check_verify_result (
667 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
670 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
671 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
679 dcp_from_frame (dcp::ArrayData const& frame, path dir)
681 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
682 create_directories (dir);
683 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
684 for (int i = 0; i < 24; ++i) {
685 writer->write (frame.data(), frame.size());
689 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
690 return write_dcp_with_single_asset (dir, reel_asset);
694 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
696 int const too_big = 1302083 * 2;
698 /* Compress a black image */
699 auto image = black_image ();
700 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
701 BOOST_REQUIRE (frame.size() < too_big);
703 /* Place it in a bigger block with some zero padding at the end */
704 dcp::ArrayData oversized_frame(too_big);
705 memcpy (oversized_frame.data(), frame.data(), frame.size());
706 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
708 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
709 prepare_directory (dir);
710 auto cpl = dcp_from_frame (oversized_frame, dir);
712 check_verify_result (
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
722 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
724 int const nearly_too_big = 1302083 * 0.98;
726 /* Compress a black image */
727 auto image = black_image ();
728 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
729 BOOST_REQUIRE (frame.size() < nearly_too_big);
731 /* Place it in a bigger block with some zero padding at the end */
732 dcp::ArrayData oversized_frame(nearly_too_big);
733 memcpy (oversized_frame.data(), frame.data(), frame.size());
734 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
736 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
737 prepare_directory (dir);
738 auto cpl = dcp_from_frame (oversized_frame, dir);
740 check_verify_result (
743 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
744 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
745 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
750 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
752 /* Compress a black image */
753 auto image = black_image ();
754 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
755 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
757 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
758 prepare_directory (dir);
759 auto cpl = dcp_from_frame (frame, dir);
761 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
765 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
767 path const dir("build/test/verify_valid_interop_subtitles");
768 prepare_directory (dir);
769 copy_file ("test/data/subs1.xml", dir / "subs.xml");
770 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
771 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
772 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
774 check_verify_result (
776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
777 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
782 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
784 using namespace boost::filesystem;
786 path const dir("build/test/verify_invalid_interop_subtitles");
787 prepare_directory (dir);
788 copy_file ("test/data/subs1.xml", dir / "subs.xml");
789 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
790 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
791 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
794 Editor e (dir / "subs.xml");
795 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
798 check_verify_result (
801 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
802 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
804 dcp::VerificationNote::Type::ERROR,
805 dcp::VerificationNote::Code::INVALID_XML,
806 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
810 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
815 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
817 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
818 prepare_directory(dir);
819 copy_file("test/data/subs4.xml", dir / "subs.xml");
820 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
821 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
822 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
824 check_verify_result (
827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
828 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
829 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
835 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
837 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
838 prepare_directory(dir);
839 copy_file("test/data/subs5.xml", dir / "subs.xml");
840 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
841 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
842 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
844 check_verify_result (
847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
854 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
856 path const dir("build/test/verify_valid_smpte_subtitles");
857 prepare_directory (dir);
858 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
859 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
860 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
861 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
867 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} }
872 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
874 using namespace boost::filesystem;
876 path const dir("build/test/verify_invalid_smpte_subtitles");
877 prepare_directory (dir);
878 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
879 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
880 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
881 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
882 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
884 check_verify_result (
887 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
889 dcp::VerificationNote::Type::ERROR,
890 dcp::VerificationNote::Code::INVALID_XML,
891 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
897 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} }
902 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
904 path const dir("build/test/verify_empty_text_node_in_subtitles");
905 prepare_directory (dir);
906 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
907 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
908 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
909 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
911 check_verify_result (
914 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
915 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
917 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
918 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} }
923 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
924 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
926 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
927 prepare_directory (dir);
928 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
929 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
930 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
931 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
933 check_verify_result (
936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
937 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
942 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
943 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
945 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
946 prepare_directory (dir);
947 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
948 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
949 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
950 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
952 check_verify_result (
955 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
956 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
957 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
958 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
963 BOOST_AUTO_TEST_CASE (verify_external_asset)
965 path const ov_dir("build/test/verify_external_asset");
966 prepare_directory (ov_dir);
968 auto image = black_image ();
969 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
970 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
971 dcp_from_frame (frame, ov_dir);
973 dcp::DCP ov (ov_dir);
976 path const vf_dir("build/test/verify_external_asset_vf");
977 prepare_directory (vf_dir);
979 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
980 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
982 check_verify_result (
985 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
991 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
993 path const dir("build/test/verify_valid_cpl_metadata");
994 prepare_directory (dir);
996 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
997 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
998 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1000 auto reel = make_shared<dcp::Reel>();
1001 reel->add (reel_asset);
1003 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1004 reel->add (simple_markers(16 * 24));
1006 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1008 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1009 cpl->set_main_sound_sample_rate (48000);
1010 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1011 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1012 cpl->set_version_number (1);
1016 dcp.set_annotation_text("hello");
1022 find_prefix(path dir, string prefix)
1024 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1025 return boost::starts_with(p.filename().string(), prefix);
1028 BOOST_REQUIRE(iter != directory_iterator());
1029 return iter->path();
1033 path find_cpl (path dir)
1035 return find_prefix(dir, "cpl_");
1042 return find_prefix(dir, "pkl_");
1047 find_asset_map(path dir)
1049 return find_prefix(dir, "ASSETMAP");
1053 /* DCP with invalid CompositionMetadataAsset */
1054 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1056 using namespace boost::filesystem;
1058 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1059 prepare_directory (dir);
1061 auto reel = make_shared<dcp::Reel>();
1062 reel->add (black_picture_asset(dir));
1063 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1065 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1066 cpl->set_main_sound_sample_rate (48000);
1067 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1068 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1069 cpl->set_version_number (1);
1071 reel->add (simple_markers());
1075 dcp.set_annotation_text("hello");
1079 Editor e (find_cpl(dir));
1080 e.replace ("MainSound", "MainSoundX");
1083 check_verify_result (
1086 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1087 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1089 dcp::VerificationNote::Type::ERROR,
1090 dcp::VerificationNote::Code::INVALID_XML,
1091 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1092 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1093 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1094 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1095 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1096 "ExtensionMetadataList?,)'"),
1097 canonical(cpl->file().get()),
1100 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1105 /* DCP with invalid CompositionMetadataAsset */
1106 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1108 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1109 prepare_directory (dir);
1111 auto reel = make_shared<dcp::Reel>();
1112 reel->add (black_picture_asset(dir));
1113 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1115 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1116 cpl->set_main_sound_sample_rate (48000);
1117 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1118 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1122 dcp.set_annotation_text("hello");
1126 Editor e (find_cpl(dir));
1127 e.replace ("meta:Width", "meta:WidthX");
1130 check_verify_result (
1132 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1137 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1139 path const dir("build/test/verify_invalid_language1");
1140 prepare_directory (dir);
1141 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1142 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1143 asset->_language = "wrong-andbad";
1144 asset->write (dir / "subs.mxf");
1145 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1146 reel_asset->_language = "badlang";
1147 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1149 check_verify_result (
1152 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1153 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1154 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1159 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1160 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1162 path const dir("build/test/verify_invalid_language2");
1163 prepare_directory (dir);
1164 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1165 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1166 asset->_language = "wrong-andbad";
1167 asset->write (dir / "subs.mxf");
1168 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1169 reel_asset->_language = "badlang";
1170 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1172 check_verify_result (
1175 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1176 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1177 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1182 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1183 * the release territory.
1185 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1187 path const dir("build/test/verify_invalid_language3");
1188 prepare_directory (dir);
1190 auto picture = simple_picture (dir, "foo");
1191 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1192 auto reel = make_shared<dcp::Reel>();
1193 reel->add (reel_picture);
1194 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1195 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1196 reel->add (reel_sound);
1197 reel->add (simple_markers());
1199 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1201 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1202 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1203 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1204 cpl->set_main_sound_sample_rate (48000);
1205 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1206 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1207 cpl->set_version_number (1);
1208 cpl->_release_territory = "fred-jim";
1209 auto dcp = make_shared<dcp::DCP>(dir);
1211 dcp->set_annotation_text("hello");
1214 check_verify_result (
1217 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1218 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1219 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1220 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1226 vector<dcp::VerificationNote>
1227 check_picture_size (int width, int height, int frame_rate, bool three_d)
1229 using namespace boost::filesystem;
1231 path dcp_path = "build/test/verify_picture_test";
1232 prepare_directory (dcp_path);
1234 shared_ptr<dcp::PictureAsset> mp;
1236 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1238 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1240 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1242 auto image = black_image (dcp::Size(width, height));
1243 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1244 int const length = three_d ? frame_rate * 2 : frame_rate;
1245 for (int i = 0; i < length; ++i) {
1246 picture_writer->write (j2c.data(), j2c.size());
1248 picture_writer->finalize ();
1250 auto d = make_shared<dcp::DCP>(dcp_path);
1251 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1252 cpl->set_annotation_text ("A Test DCP");
1253 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1254 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1255 cpl->set_main_sound_sample_rate (48000);
1256 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1257 cpl->set_main_picture_active_area(dcp::Size(width, height));
1258 cpl->set_version_number (1);
1260 auto reel = make_shared<dcp::Reel>();
1263 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1265 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1268 reel->add (simple_markers(frame_rate));
1273 d->set_annotation_text("A Test DCP");
1276 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1282 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1284 auto notes = check_picture_size(width, height, frame_rate, three_d);
1285 BOOST_CHECK_EQUAL (notes.size(), 0U);
1291 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1293 auto notes = check_picture_size(width, height, frame_rate, three_d);
1294 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1295 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1296 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1302 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1304 auto notes = check_picture_size(width, height, frame_rate, three_d);
1305 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1306 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1307 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1313 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1315 auto notes = check_picture_size(width, height, frame_rate, three_d);
1316 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1317 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1318 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1322 BOOST_AUTO_TEST_CASE (verify_picture_size)
1324 using namespace boost::filesystem;
1327 check_picture_size_ok (2048, 858, 24, false);
1328 check_picture_size_ok (2048, 858, 25, false);
1329 check_picture_size_ok (2048, 858, 48, false);
1330 check_picture_size_ok (2048, 858, 24, true);
1331 check_picture_size_ok (2048, 858, 25, true);
1332 check_picture_size_ok (2048, 858, 48, true);
1335 check_picture_size_ok (1998, 1080, 24, false);
1336 check_picture_size_ok (1998, 1080, 25, false);
1337 check_picture_size_ok (1998, 1080, 48, false);
1338 check_picture_size_ok (1998, 1080, 24, true);
1339 check_picture_size_ok (1998, 1080, 25, true);
1340 check_picture_size_ok (1998, 1080, 48, true);
1343 check_picture_size_ok (4096, 1716, 24, false);
1346 check_picture_size_ok (3996, 2160, 24, false);
1348 /* Bad frame size */
1349 check_picture_size_bad_frame_size (2050, 858, 24, false);
1350 check_picture_size_bad_frame_size (2048, 658, 25, false);
1351 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1352 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1354 /* Bad 2K frame rate */
1355 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1356 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1357 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1359 /* Bad 4K frame rate */
1360 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1361 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1364 auto notes = check_picture_size(3996, 2160, 24, true);
1365 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1366 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1367 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1373 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")
1376 std::make_shared<dcp::SubtitleString>(
1384 dcp::Time(start_frame, 24, 24),
1385 dcp::Time(end_frame, 24, 24),
1387 dcp::HAlign::CENTER,
1391 dcp::Direction::LTR,
1403 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1405 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1406 prepare_directory (dir);
1408 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1409 for (int i = 0; i < 2048; ++i) {
1410 add_test_subtitle (asset, i * 24, i * 24 + 20);
1412 asset->set_language (dcp::LanguageTag("de-DE"));
1413 asset->write (dir / "subs.mxf");
1414 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1415 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1417 check_verify_result (
1420 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1422 dcp::VerificationNote::Type::BV21_ERROR,
1423 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1425 canonical(dir / "subs.mxf")
1427 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1428 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1434 shared_ptr<dcp::SMPTESubtitleAsset>
1435 make_large_subtitle_asset (path font_file)
1437 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1438 dcp::ArrayData big_fake_font(1024 * 1024);
1439 big_fake_font.write (font_file);
1440 for (int i = 0; i < 116; ++i) {
1441 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1449 verify_timed_text_asset_too_large (string name)
1451 auto const dir = path("build/test") / name;
1452 prepare_directory (dir);
1453 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1454 add_test_subtitle (asset, 0, 240);
1455 asset->set_language (dcp::LanguageTag("de-DE"));
1456 asset->write (dir / "subs.mxf");
1458 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1459 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1461 check_verify_result (
1464 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695532"), canonical(dir / "subs.mxf") },
1465 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1466 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1467 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1468 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1473 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1475 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1476 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1480 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1482 path dir = "build/test/verify_missing_subtitle_language";
1483 prepare_directory (dir);
1484 auto dcp = make_simple (dir, 1, 106);
1487 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1488 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1489 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1490 "<ContentTitleText>Content</ContentTitleText>"
1491 "<AnnotationText>Annotation</AnnotationText>"
1492 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1493 "<ReelNumber>1</ReelNumber>"
1494 "<EditRate>24 1</EditRate>"
1495 "<TimeCodeRate>24</TimeCodeRate>"
1496 "<StartTime>00:00:00:00</StartTime>"
1497 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1499 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1500 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1501 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1507 dcp::File xml_file(dir / "subs.xml", "w");
1508 BOOST_REQUIRE (xml_file);
1509 xml_file.write(xml.c_str(), xml.size(), 1);
1511 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1512 subs->write (dir / "subs.mxf");
1514 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1515 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1516 dcp->set_annotation_text("A Test DCP");
1519 check_verify_result (
1522 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1523 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1528 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1530 path path ("build/test/verify_mismatched_subtitle_languages");
1531 auto constexpr reel_length = 192;
1532 auto dcp = make_simple (path, 2, reel_length);
1533 auto cpl = dcp->cpls()[0];
1536 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1537 subs->set_language (dcp::LanguageTag("de-DE"));
1538 subs->add (simple_subtitle());
1539 subs->write (path / "subs1.mxf");
1540 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1541 cpl->reels()[0]->add(reel_subs);
1545 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1546 subs->set_language (dcp::LanguageTag("en-US"));
1547 subs->add (simple_subtitle());
1548 subs->write (path / "subs2.mxf");
1549 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1550 cpl->reels()[1]->add(reel_subs);
1553 dcp->set_annotation_text("A Test DCP");
1556 check_verify_result (
1559 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1560 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1561 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1566 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1568 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1569 auto constexpr reel_length = 192;
1570 auto dcp = make_simple (path, 2, reel_length);
1571 auto cpl = dcp->cpls()[0];
1574 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1575 ccaps->set_language (dcp::LanguageTag("de-DE"));
1576 ccaps->add (simple_subtitle());
1577 ccaps->write (path / "subs1.mxf");
1578 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1579 cpl->reels()[0]->add(reel_ccaps);
1583 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1584 ccaps->set_language (dcp::LanguageTag("en-US"));
1585 ccaps->add (simple_subtitle());
1586 ccaps->write (path / "subs2.mxf");
1587 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1588 cpl->reels()[1]->add(reel_ccaps);
1591 dcp->set_annotation_text("A Test DCP");
1594 check_verify_result (
1597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1598 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1603 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1605 path dir = "build/test/verify_missing_subtitle_start_time";
1606 prepare_directory (dir);
1607 auto dcp = make_simple (dir, 1, 106);
1610 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1611 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1612 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1613 "<ContentTitleText>Content</ContentTitleText>"
1614 "<AnnotationText>Annotation</AnnotationText>"
1615 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1616 "<ReelNumber>1</ReelNumber>"
1617 "<Language>de-DE</Language>"
1618 "<EditRate>24 1</EditRate>"
1619 "<TimeCodeRate>24</TimeCodeRate>"
1620 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1622 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1623 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1624 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1630 dcp::File xml_file(dir / "subs.xml", "w");
1631 BOOST_REQUIRE (xml_file);
1632 xml_file.write(xml.c_str(), xml.size(), 1);
1634 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1635 subs->write (dir / "subs.mxf");
1637 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1638 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1639 dcp->set_annotation_text("A Test DCP");
1642 check_verify_result (
1645 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1646 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1651 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1653 path dir = "build/test/verify_invalid_subtitle_start_time";
1654 prepare_directory (dir);
1655 auto dcp = make_simple (dir, 1, 106);
1658 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1659 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1660 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1661 "<ContentTitleText>Content</ContentTitleText>"
1662 "<AnnotationText>Annotation</AnnotationText>"
1663 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1664 "<ReelNumber>1</ReelNumber>"
1665 "<Language>de-DE</Language>"
1666 "<EditRate>24 1</EditRate>"
1667 "<TimeCodeRate>24</TimeCodeRate>"
1668 "<StartTime>00:00:02:00</StartTime>"
1669 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1671 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1672 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1673 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1679 dcp::File xml_file(dir / "subs.xml", "w");
1680 BOOST_REQUIRE (xml_file);
1681 xml_file.write(xml.c_str(), xml.size(), 1);
1683 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1684 subs->write (dir / "subs.mxf");
1686 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1687 dcp->cpls().front()->reels().front()->add(reel_subs);
1688 dcp->set_annotation_text("A Test DCP");
1691 check_verify_result (
1694 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1695 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1703 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1706 , v_position(v_position_)
1714 dcp::VAlign v_align;
1720 shared_ptr<dcp::CPL>
1721 dcp_with_text (path dir, vector<TestText> subs)
1723 prepare_directory (dir);
1724 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1725 asset->set_start_time (dcp::Time());
1726 for (auto i: subs) {
1727 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1729 asset->set_language (dcp::LanguageTag("de-DE"));
1730 asset->write (dir / "subs.mxf");
1732 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1733 return write_dcp_with_single_asset (dir, reel_asset);
1738 shared_ptr<dcp::CPL>
1739 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1741 prepare_directory (dir);
1742 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1743 asset->set_start_time (dcp::Time());
1744 asset->set_language (dcp::LanguageTag("de-DE"));
1746 auto subs_mxf = dir / "subs.mxf";
1747 asset->write (subs_mxf);
1749 /* The call to write() puts the asset into the DCP correctly but it will have
1750 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1753 ASDCP::TimedText::MXFWriter writer;
1754 ASDCP::WriterInfo writer_info;
1755 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1757 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1758 DCP_ASSERT (c == Kumu::UUID_Length);
1759 ASDCP::TimedText::TimedTextDescriptor descriptor;
1760 descriptor.ContainerDuration = asset->intrinsic_duration();
1761 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1762 DCP_ASSERT (c == Kumu::UUID_Length);
1763 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1764 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1765 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1766 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1769 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1770 return write_dcp_with_single_asset (dir, reel_asset);
1774 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1776 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1777 /* Just too early */
1778 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1779 check_verify_result (
1782 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1783 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1789 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1791 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1792 /* Just late enough */
1793 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1794 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1798 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1800 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1801 prepare_directory (dir);
1803 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1804 asset1->set_start_time (dcp::Time());
1805 /* Just late enough */
1806 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1807 asset1->set_language (dcp::LanguageTag("de-DE"));
1808 asset1->write (dir / "subs1.mxf");
1809 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1810 auto reel1 = make_shared<dcp::Reel>();
1811 reel1->add (reel_asset1);
1812 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1813 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1814 reel1->add (markers1);
1816 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1817 asset2->set_start_time (dcp::Time());
1818 /* This would be too early on first reel but should be OK on the second */
1819 add_test_subtitle (asset2, 3, 4 * 24);
1820 asset2->set_language (dcp::LanguageTag("de-DE"));
1821 asset2->write (dir / "subs2.mxf");
1822 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1823 auto reel2 = make_shared<dcp::Reel>();
1824 reel2->add (reel_asset2);
1825 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1826 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1827 reel2->add (markers2);
1829 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1832 auto dcp = make_shared<dcp::DCP>(dir);
1834 dcp->set_annotation_text("hello");
1837 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1841 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1843 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1844 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1848 { 5 * 24 + 1, 6 * 24 },
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1859 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1861 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1862 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1866 { 5 * 24 + 16, 8 * 24 },
1868 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1872 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1874 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1875 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1876 check_verify_result (
1879 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1880 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1885 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1887 auto const dir = path("build/test/verify_valid_subtitle_duration");
1888 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1889 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1893 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1895 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1896 prepare_directory (dir);
1897 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1898 asset->set_start_time (dcp::Time());
1899 add_test_subtitle (asset, 0, 4 * 24);
1900 asset->set_language (dcp::LanguageTag("de-DE"));
1901 asset->write (dir / "subs.mxf");
1903 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1904 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1905 check_verify_result (
1908 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1909 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1910 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1917 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1919 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1920 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1923 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1924 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1925 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1926 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1928 check_verify_result (
1931 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1932 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1937 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1939 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1940 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1943 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1944 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1945 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1947 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1951 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1953 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1954 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1957 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1958 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1959 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1960 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1962 check_verify_result (
1965 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1971 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1973 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1974 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1977 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1978 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1979 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1980 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1982 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1986 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1988 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1989 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1992 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1994 check_verify_result (
1997 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1998 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2003 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2005 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2006 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2009 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2011 check_verify_result (
2014 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2020 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2022 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2023 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2026 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2027 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2028 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2029 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2031 check_verify_result (
2034 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2035 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2040 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2042 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2043 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2046 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2047 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2048 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2050 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2054 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2056 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2057 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2060 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2061 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2062 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2063 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2074 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2076 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2077 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2080 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2081 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2082 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2083 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2085 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2089 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2091 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2092 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2095 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2097 check_verify_result (
2100 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2105 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2107 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2108 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2111 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2113 check_verify_result (
2116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2117 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2122 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2124 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2125 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2128 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2129 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2130 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2132 check_verify_result (
2135 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2140 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2142 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2143 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2146 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2147 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2148 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2150 check_verify_result (
2153 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2154 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2159 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2161 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2162 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2165 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2166 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2167 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2169 check_verify_result (
2172 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2177 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2179 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2180 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2183 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2184 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2185 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2187 check_verify_result (
2190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2195 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2197 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2198 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2199 check_verify_result (
2202 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2203 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2208 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2210 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2211 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2221 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2223 path const dir("build/test/verify_invalid_sound_frame_rate");
2224 prepare_directory (dir);
2226 auto picture = simple_picture (dir, "foo");
2227 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2228 auto reel = make_shared<dcp::Reel>();
2229 reel->add (reel_picture);
2230 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2231 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2232 reel->add (reel_sound);
2233 reel->add (simple_markers());
2234 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2236 auto dcp = make_shared<dcp::DCP>(dir);
2238 dcp->set_annotation_text("hello");
2241 check_verify_result (
2244 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2245 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2250 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2252 path const dir("build/test/verify_missing_cpl_annotation_text");
2253 auto dcp = make_simple (dir);
2254 dcp->set_annotation_text("A Test DCP");
2257 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2259 auto const cpl = dcp->cpls()[0];
2262 BOOST_REQUIRE (cpl->file());
2263 Editor e(cpl->file().get());
2264 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2267 check_verify_result (
2270 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2271 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2276 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2278 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2279 auto dcp = make_simple (dir);
2280 dcp->set_annotation_text("A Test DCP");
2283 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2284 auto const cpl = dcp->cpls()[0];
2287 BOOST_REQUIRE (cpl->file());
2288 Editor e(cpl->file().get());
2289 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2292 check_verify_result (
2295 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2296 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2301 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2303 path const dir("build/test/verify_mismatched_asset_duration");
2304 prepare_directory (dir);
2305 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2306 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2308 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2309 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2311 auto reel = make_shared<dcp::Reel>(
2312 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2313 make_shared<dcp::ReelSoundAsset>(ms, 0)
2316 reel->add (simple_markers());
2320 dcp->set_annotation_text("A Test DCP");
2323 check_verify_result (
2326 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2334 shared_ptr<dcp::CPL>
2335 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2337 prepare_directory (dir);
2338 auto dcp = make_shared<dcp::DCP>(dir);
2339 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2341 auto constexpr reel_length = 192;
2343 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2344 subs->set_language (dcp::LanguageTag("de-DE"));
2345 subs->set_start_time (dcp::Time());
2346 subs->add (simple_subtitle());
2347 subs->write (dir / "subs.mxf");
2348 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2350 auto reel1 = make_shared<dcp::Reel>(
2351 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2352 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2356 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2359 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2360 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2361 reel1->add (markers1);
2365 auto reel2 = make_shared<dcp::Reel>(
2366 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2367 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2371 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2374 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2375 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2376 reel2->add (markers2);
2381 dcp->set_annotation_text("A Test DCP");
2388 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2391 path dir ("build/test/missing_main_subtitle_from_some_reels");
2392 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2393 check_verify_result (
2396 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2397 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2403 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2404 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2405 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2409 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2410 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2411 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2417 shared_ptr<dcp::CPL>
2418 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2420 prepare_directory (dir);
2421 auto dcp = make_shared<dcp::DCP>(dir);
2422 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2424 auto constexpr reel_length = 192;
2426 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2427 subs->set_language (dcp::LanguageTag("de-DE"));
2428 subs->set_start_time (dcp::Time());
2429 subs->add (simple_subtitle());
2430 subs->write (dir / "subs.mxf");
2432 auto reel1 = make_shared<dcp::Reel>(
2433 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2434 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2437 for (int i = 0; i < caps_in_reel1; ++i) {
2438 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2441 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2442 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2443 reel1->add (markers1);
2447 auto reel2 = make_shared<dcp::Reel>(
2448 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2449 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2452 for (int i = 0; i < caps_in_reel2; ++i) {
2453 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2456 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2457 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2458 reel2->add (markers2);
2463 dcp->set_annotation_text("A Test DCP");
2470 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2473 path dir ("build/test/mismatched_closed_caption_asset_counts");
2474 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2475 check_verify_result (
2478 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2479 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2484 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2485 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2486 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2490 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2491 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2492 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2499 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2501 prepare_directory (dir);
2502 auto dcp = make_shared<dcp::DCP>(dir);
2503 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2505 auto constexpr reel_length = 192;
2507 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2508 subs->set_language (dcp::LanguageTag("de-DE"));
2509 subs->set_start_time (dcp::Time());
2510 subs->add (simple_subtitle());
2511 subs->write (dir / "subs.mxf");
2512 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2515 auto reel = make_shared<dcp::Reel>(
2516 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2517 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2520 reel->add (reel_text);
2522 reel->add (simple_markers(reel_length));
2527 dcp->set_annotation_text("A Test DCP");
2530 check_verify_result (
2533 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2539 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2541 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2542 "build/test/verify_subtitle_entry_point_must_be_present",
2543 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2544 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2545 asset->unset_entry_point ();
2549 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2550 "build/test/verify_subtitle_entry_point_must_be_zero",
2551 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2552 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2553 asset->set_entry_point (4);
2557 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2558 "build/test/verify_closed_caption_entry_point_must_be_present",
2559 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2560 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2561 asset->unset_entry_point ();
2565 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2566 "build/test/verify_closed_caption_entry_point_must_be_zero",
2567 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2568 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2569 asset->set_entry_point (9);
2575 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2579 path const dir("build/test/verify_missing_hash");
2580 auto dcp = make_simple (dir);
2581 dcp->set_annotation_text("A Test DCP");
2584 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2585 auto const cpl = dcp->cpls()[0];
2586 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2587 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2588 auto asset_id = cpl->reels()[0]->main_picture()->id();
2591 BOOST_REQUIRE (cpl->file());
2592 Editor e(cpl->file().get());
2593 e.delete_first_line_containing("<Hash>");
2596 check_verify_result (
2599 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2600 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2607 verify_markers_test (
2609 vector<pair<dcp::Marker, dcp::Time>> markers,
2610 vector<dcp::VerificationNote> test_notes
2613 auto dcp = make_simple (dir);
2614 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2615 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2616 for (auto const& i: markers) {
2617 markers_asset->set (i.first, i.second);
2619 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2620 dcp->set_annotation_text("A Test DCP");
2623 check_verify_result ({dir}, test_notes);
2627 BOOST_AUTO_TEST_CASE (verify_markers)
2629 verify_markers_test (
2630 "build/test/verify_markers_all_correct",
2632 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2633 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2634 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2635 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2640 verify_markers_test (
2641 "build/test/verify_markers_missing_ffec",
2643 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2644 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2645 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2648 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2651 verify_markers_test (
2652 "build/test/verify_markers_missing_ffmc",
2654 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2655 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2656 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2659 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2662 verify_markers_test (
2663 "build/test/verify_markers_missing_ffoc",
2665 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2666 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2667 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2670 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2673 verify_markers_test (
2674 "build/test/verify_markers_missing_lfoc",
2676 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2677 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2678 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2681 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2684 verify_markers_test (
2685 "build/test/verify_markers_incorrect_ffoc",
2687 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2688 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2689 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2690 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2693 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2696 verify_markers_test (
2697 "build/test/verify_markers_incorrect_lfoc",
2699 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2700 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2701 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2702 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2705 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2710 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2712 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2713 prepare_directory (dir);
2714 auto dcp = make_simple (dir);
2715 auto cpl = dcp->cpls()[0];
2716 cpl->unset_version_number();
2717 dcp->set_annotation_text("A Test DCP");
2720 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2724 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2726 path dir = "build/test/verify_missing_extension_metadata1";
2727 auto dcp = make_simple (dir);
2728 dcp->set_annotation_text("A Test DCP");
2731 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2732 auto cpl = dcp->cpls()[0];
2735 Editor e (cpl->file().get());
2736 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2739 check_verify_result (
2742 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2743 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2748 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2750 path dir = "build/test/verify_missing_extension_metadata2";
2751 auto dcp = make_simple (dir);
2752 dcp->set_annotation_text("A Test DCP");
2755 auto cpl = dcp->cpls()[0];
2758 Editor e (cpl->file().get());
2759 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2762 check_verify_result (
2765 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2766 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2771 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2773 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2774 auto dcp = make_simple (dir);
2775 dcp->set_annotation_text("A Test DCP");
2778 auto const cpl = dcp->cpls()[0];
2781 Editor e (cpl->file().get());
2782 e.replace ("<meta:Name>A", "<meta:NameX>A");
2783 e.replace ("n</meta:Name>", "n</meta:NameX>");
2786 check_verify_result (
2789 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2790 { 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 },
2791 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2796 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2798 path dir = "build/test/verify_invalid_extension_metadata1";
2799 auto dcp = make_simple (dir);
2800 dcp->set_annotation_text("A Test DCP");
2803 auto cpl = dcp->cpls()[0];
2806 Editor e (cpl->file().get());
2807 e.replace ("Application", "Fred");
2810 check_verify_result (
2813 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2814 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2819 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2821 path dir = "build/test/verify_invalid_extension_metadata2";
2822 auto dcp = make_simple (dir);
2823 dcp->set_annotation_text("A Test DCP");
2826 auto cpl = dcp->cpls()[0];
2829 Editor e (cpl->file().get());
2830 e.replace ("DCP Constraints Profile", "Fred");
2833 check_verify_result (
2836 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2837 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2842 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2844 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2845 auto dcp = make_simple (dir);
2846 dcp->set_annotation_text("A Test DCP");
2849 auto const cpl = dcp->cpls()[0];
2852 Editor e (cpl->file().get());
2853 e.replace ("<meta:Value>", "<meta:ValueX>");
2854 e.replace ("</meta:Value>", "</meta:ValueX>");
2857 check_verify_result (
2860 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2861 { 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 },
2862 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2867 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2869 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2870 auto dcp = make_simple (dir);
2871 dcp->set_annotation_text("A Test DCP");
2874 auto const cpl = dcp->cpls()[0];
2877 Editor e (cpl->file().get());
2878 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2881 check_verify_result (
2884 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2885 { 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() },
2890 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2892 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2893 auto dcp = make_simple (dir);
2894 dcp->set_annotation_text("A Test DCP");
2897 auto const cpl = dcp->cpls()[0];
2900 Editor e (cpl->file().get());
2901 e.replace ("<meta:Property>", "<meta:PropertyX>");
2902 e.replace ("</meta:Property>", "</meta:PropertyX>");
2905 check_verify_result (
2908 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2909 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2910 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2915 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2917 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2918 auto dcp = make_simple (dir);
2919 dcp->set_annotation_text("A Test DCP");
2922 auto const cpl = dcp->cpls()[0];
2925 Editor e (cpl->file().get());
2926 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2927 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2930 check_verify_result (
2933 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2934 { 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 },
2935 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2941 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2943 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2944 prepare_directory (dir);
2945 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2946 copy_file (i.path(), dir / i.path().filename());
2949 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2950 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2954 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2957 check_verify_result (
2960 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2961 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2962 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2963 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2964 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2965 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2967 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2972 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2974 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2975 prepare_directory (dir);
2976 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2977 copy_file (i.path(), dir / i.path().filename());
2980 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2981 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2984 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2987 check_verify_result (
2990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2992 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2993 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2994 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2996 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
3001 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3003 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3004 prepare_directory (dir);
3005 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3006 copy_file (i.path(), dir / i.path().filename());
3010 Editor e (dir / dcp_test1_pkl);
3011 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3014 check_verify_result ({dir}, {});
3018 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3020 path dir ("build/test/verify_must_not_be_partially_encrypted");
3021 prepare_directory (dir);
3025 auto signer = make_shared<dcp::CertificateChain>();
3026 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3027 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3028 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3029 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3031 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3035 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3038 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3039 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3040 for (int i = 0; i < 24; ++i) {
3041 writer->write (j2c.data(), j2c.size());
3043 writer->finalize ();
3045 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3047 auto reel = make_shared<dcp::Reel>(
3048 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3049 make_shared<dcp::ReelSoundAsset>(ms, 0)
3052 reel->add (simple_markers());
3056 cpl->set_content_version (
3057 {"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"}
3059 cpl->set_annotation_text ("A Test DCP");
3060 cpl->set_issuer ("OpenDCP 0.0.25");
3061 cpl->set_creator ("OpenDCP 0.0.25");
3062 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3063 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3064 cpl->set_main_sound_sample_rate (48000);
3065 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3066 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3067 cpl->set_version_number (1);
3071 d.set_issuer("OpenDCP 0.0.25");
3072 d.set_creator("OpenDCP 0.0.25");
3073 d.set_issue_date("2012-07-17T04:45:18+00:00");
3074 d.set_annotation_text("A Test DCP");
3075 d.write_xml(signer);
3077 check_verify_result (
3080 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3085 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3087 vector<dcp::VerificationNote> notes;
3088 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"));
3089 auto reader = picture.start_read ();
3090 auto frame = reader->get_frame (0);
3091 verify_j2k(frame, 0, 24, notes);
3092 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3096 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3098 vector<dcp::VerificationNote> notes;
3099 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3100 auto reader = picture.start_read ();
3101 auto frame = reader->get_frame (0);
3102 verify_j2k(frame, 0, 24, notes);
3103 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3107 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3109 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3110 prepare_directory (dir);
3111 auto dcp = make_simple (dir);
3113 vector<dcp::VerificationNote> notes;
3114 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3115 auto reader = picture.start_read ();
3116 auto frame = reader->get_frame (0);
3117 verify_j2k(frame, 0, 24, notes);
3118 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3122 /** Check that ResourceID and the XML ID being different is spotted */
3123 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3125 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3126 prepare_directory (dir);
3128 ASDCP::WriterInfo writer_info;
3129 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3132 auto mxf_id = dcp::make_uuid ();
3133 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3134 BOOST_REQUIRE (c == Kumu::UUID_Length);
3136 auto resource_id = dcp::make_uuid ();
3137 ASDCP::TimedText::TimedTextDescriptor descriptor;
3138 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3139 DCP_ASSERT (c == Kumu::UUID_Length);
3141 auto xml_id = dcp::make_uuid ();
3142 ASDCP::TimedText::MXFWriter writer;
3143 auto subs_mxf = dir / "subs.mxf";
3144 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3145 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3146 writer.WriteTimedTextResource (dcp::String::compose(
3147 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3148 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3149 "<Id>urn:uuid:%1</Id>"
3150 "<ContentTitleText>Content</ContentTitleText>"
3151 "<AnnotationText>Annotation</AnnotationText>"
3152 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3153 "<ReelNumber>1</ReelNumber>"
3154 "<Language>en-US</Language>"
3155 "<EditRate>25 1</EditRate>"
3156 "<TimeCodeRate>25</TimeCodeRate>"
3157 "<StartTime>00:00:00:00</StartTime>"
3159 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3160 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3161 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3170 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3171 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3173 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3175 check_verify_result (
3178 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3179 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3180 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3181 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3186 /** Check that ResourceID and the MXF ID being the same is spotted */
3187 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3189 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3190 prepare_directory (dir);
3192 ASDCP::WriterInfo writer_info;
3193 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3196 auto mxf_id = dcp::make_uuid ();
3197 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3198 BOOST_REQUIRE (c == Kumu::UUID_Length);
3200 auto resource_id = mxf_id;
3201 ASDCP::TimedText::TimedTextDescriptor descriptor;
3202 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3203 DCP_ASSERT (c == Kumu::UUID_Length);
3205 auto xml_id = resource_id;
3206 ASDCP::TimedText::MXFWriter writer;
3207 auto subs_mxf = dir / "subs.mxf";
3208 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3209 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3210 writer.WriteTimedTextResource (dcp::String::compose(
3211 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3212 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3213 "<Id>urn:uuid:%1</Id>"
3214 "<ContentTitleText>Content</ContentTitleText>"
3215 "<AnnotationText>Annotation</AnnotationText>"
3216 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3217 "<ReelNumber>1</ReelNumber>"
3218 "<Language>en-US</Language>"
3219 "<EditRate>25 1</EditRate>"
3220 "<TimeCodeRate>25</TimeCodeRate>"
3221 "<StartTime>00:00:00:00</StartTime>"
3223 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3224 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3225 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3234 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3235 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3237 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3239 check_verify_result (
3242 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3243 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3244 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3245 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3246 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3251 /** Check a DCP with a 3D asset marked as 2D */
3252 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3254 check_verify_result (
3255 { private_test / "data" / "xm" },
3258 dcp::VerificationNote::Type::WARNING,
3259 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3262 dcp::VerificationNote::Type::BV21_ERROR,
3263 dcp::VerificationNote::Code::INVALID_STANDARD
3270 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3272 path dir = "build/test/verify_unexpected_things_in_main_markers";
3273 prepare_directory (dir);
3274 auto dcp = make_simple (dir, 1, 24);
3275 dcp->set_annotation_text("A Test DCP");
3279 Editor e (find_cpl(dir));
3281 " <IntrinsicDuration>24</IntrinsicDuration>",
3282 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3286 dcp::CPL cpl (find_cpl(dir));
3288 check_verify_result (
3291 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3292 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3293 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3298 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3300 path dir = "build/test/verify_invalid_content_kind";
3301 prepare_directory (dir);
3302 auto dcp = make_simple (dir, 1, 24);
3303 dcp->set_annotation_text("A Test DCP");
3307 Editor e(find_cpl(dir));
3308 e.replace("trailer", "trip");
3311 dcp::CPL cpl (find_cpl(dir));
3313 check_verify_result (
3316 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3317 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3323 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3325 path dir = "build/test/verify_valid_content_kind";
3326 prepare_directory (dir);
3327 auto dcp = make_simple (dir, 1, 24);
3328 dcp->set_annotation_text("A Test DCP");
3332 Editor e(find_cpl(dir));
3333 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3336 dcp::CPL cpl (find_cpl(dir));
3338 check_verify_result (
3341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3347 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3349 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3350 prepare_directory(dir);
3351 auto dcp = make_simple(dir, 1, 24);
3354 auto constexpr area = "<meta:MainPictureActiveArea>";
3357 Editor e(find_cpl(dir));
3358 e.delete_lines_after(area, 2);
3359 e.insert(area, "<meta:Height>4080</meta:Height>");
3360 e.insert(area, "<meta:Width>1997</meta:Width>");
3363 dcp::PKL pkl(find_pkl(dir));
3364 dcp::CPL cpl(find_cpl(dir));
3366 check_verify_result(
3369 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3370 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3371 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3372 { 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)) },
3377 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3379 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3380 prepare_directory(dir);
3381 auto dcp = make_simple(dir, 1, 24);
3384 auto constexpr area = "<meta:MainPictureActiveArea>";
3387 Editor e(find_cpl(dir));
3388 e.delete_lines_after(area, 2);
3389 e.insert(area, "<meta:Height>5125</meta:Height>");
3390 e.insert(area, "<meta:Width>9900</meta:Width>");
3393 dcp::PKL pkl(find_pkl(dir));
3394 dcp::CPL cpl(find_cpl(dir));
3396 check_verify_result(
3399 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3400 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3401 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3402 { 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)) },
3403 { 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)) },
3408 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3412 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3413 prepare_directory(dir);
3414 auto dcp = make_simple(dir, 1, 24);
3418 Editor e(find_pkl(dir));
3419 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3422 dcp::PKL pkl(find_pkl(dir));
3424 check_verify_result(
3427 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3432 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3436 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3437 prepare_directory(dir);
3438 auto dcp = make_simple(dir, 1, 24);
3442 Editor e(find_asset_map(dir));
3443 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3446 dcp::PKL pkl(find_pkl(dir));
3447 dcp::AssetMap asset_map(find_asset_map(dir));
3449 check_verify_result(
3452 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3453 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3454 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3459 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3461 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3463 dcp::MXFMetadata mxf_meta;
3464 mxf_meta.company_name = "OpenDCP";
3465 mxf_meta.product_name = "OpenDCP";
3466 mxf_meta.product_version = "0.0.25";
3468 auto constexpr sample_rate = 48000;
3469 auto constexpr frames = 240;
3471 boost::filesystem::remove_all(path);
3472 boost::filesystem::create_directories(path);
3473 auto dcp = make_shared<dcp::DCP>(path);
3474 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3475 cpl->set_annotation_text("hello");
3476 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3477 cpl->set_main_sound_sample_rate(sample_rate);
3478 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3479 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3480 cpl->set_version_number(1);
3484 /* Reel with 2 channels of audio */
3486 auto mp = simple_picture(path, "1", frames, {});
3487 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3489 auto reel = make_shared<dcp::Reel>(
3490 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3491 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3494 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3495 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3502 /* Reel with 6 channels of audio */
3504 auto mp = simple_picture(path, "2", frames, {});
3505 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3507 auto reel = make_shared<dcp::Reel>(
3508 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3509 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3512 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3513 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3520 dcp->set_annotation_text("hello");
3523 check_verify_result(
3526 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3531 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3533 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3535 dcp::MXFMetadata mxf_meta;
3536 mxf_meta.company_name = "OpenDCP";
3537 mxf_meta.product_name = "OpenDCP";
3538 mxf_meta.product_version = "0.0.25";
3540 auto constexpr sample_rate = 48000;
3541 auto constexpr frames = 240;
3543 boost::filesystem::remove_all(path);
3544 boost::filesystem::create_directories(path);
3545 auto dcp = make_shared<dcp::DCP>(path);
3546 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3547 cpl->set_annotation_text("hello");
3548 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3549 cpl->set_main_sound_sample_rate(sample_rate);
3550 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3551 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3552 cpl->set_version_number(1);
3554 auto mp = simple_picture(path, "1", frames, {});
3555 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3557 auto reel = make_shared<dcp::Reel>(
3558 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3559 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3562 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3563 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3564 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3570 dcp->set_annotation_text("hello");
3573 check_verify_result(
3576 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_SOUND_CONFIGURATION, std::string{"MainSoundConfiguration has 6 channels but sound assets have 2"}, canonical(find_cpl(path)) },
3581 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3583 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3584 auto constexpr video_frames = 24;
3585 auto constexpr sample_rate = 48000;
3587 boost::filesystem::remove_all(path);
3588 boost::filesystem::create_directories(path);
3590 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3591 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3593 dcp::Size const size(1998, 1080);
3594 auto image = make_shared<dcp::OpenJPEGImage>(size);
3595 boost::random::mt19937 rng(1);
3596 boost::random::uniform_int_distribution<> dist(0, 4095);
3597 for (int c = 0; c < 3; ++c) {
3598 for (int p = 0; p < (1998 * 1080); ++p) {
3599 image->data(c)[p] = dist(rng);
3602 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3603 for (int i = 0; i < 24; ++i) {
3604 picture_writer->write(j2c.data(), j2c.size());
3606 picture_writer->finalize();
3608 auto dcp = make_shared<dcp::DCP>(path);
3609 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3610 cpl->set_content_version(
3611 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3613 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3614 cpl->set_main_sound_sample_rate(sample_rate);
3615 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3616 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3617 cpl->set_version_number(1);
3619 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3621 auto reel = make_shared<dcp::Reel>(
3622 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3623 make_shared<dcp::ReelSoundAsset>(ms, 0)
3628 dcp->set_annotation_text("A Test DCP");
3631 check_verify_result(
3634 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3635 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3636 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },