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"} },
868 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
873 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
875 using namespace boost::filesystem;
877 path const dir("build/test/verify_invalid_smpte_subtitles");
878 prepare_directory (dir);
879 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
880 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
881 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
882 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
883 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
885 check_verify_result (
888 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
890 dcp::VerificationNote::Type::ERROR,
891 dcp::VerificationNote::Code::INVALID_XML,
892 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
898 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
899 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
904 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
906 path const dir("build/test/verify_empty_text_node_in_subtitles");
907 prepare_directory (dir);
908 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
909 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
910 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
911 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
913 check_verify_result (
916 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
917 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
920 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
921 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
926 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
927 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
929 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
930 prepare_directory (dir);
931 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
932 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
933 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
934 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
936 check_verify_result (
939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
940 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
945 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
946 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
948 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
949 prepare_directory (dir);
950 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
951 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
952 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
953 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
955 check_verify_result (
958 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
959 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
960 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
961 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
966 BOOST_AUTO_TEST_CASE (verify_external_asset)
968 path const ov_dir("build/test/verify_external_asset");
969 prepare_directory (ov_dir);
971 auto image = black_image ();
972 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
973 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
974 dcp_from_frame (frame, ov_dir);
976 dcp::DCP ov (ov_dir);
979 path const vf_dir("build/test/verify_external_asset_vf");
980 prepare_directory (vf_dir);
982 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
983 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
985 check_verify_result (
988 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
994 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
996 path const dir("build/test/verify_valid_cpl_metadata");
997 prepare_directory (dir);
999 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1000 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1001 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1003 auto reel = make_shared<dcp::Reel>();
1004 reel->add (reel_asset);
1006 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1007 reel->add (simple_markers(16 * 24));
1009 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1011 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1012 cpl->set_main_sound_sample_rate (48000);
1013 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1014 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1015 cpl->set_version_number (1);
1019 dcp.set_annotation_text("hello");
1025 find_prefix(path dir, string prefix)
1027 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1028 return boost::starts_with(p.filename().string(), prefix);
1031 BOOST_REQUIRE(iter != directory_iterator());
1032 return iter->path();
1036 path find_cpl (path dir)
1038 return find_prefix(dir, "cpl_");
1045 return find_prefix(dir, "pkl_");
1050 find_asset_map(path dir)
1052 return find_prefix(dir, "ASSETMAP");
1056 /* DCP with invalid CompositionMetadataAsset */
1057 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1059 using namespace boost::filesystem;
1061 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1062 prepare_directory (dir);
1064 auto reel = make_shared<dcp::Reel>();
1065 reel->add (black_picture_asset(dir));
1066 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1068 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1069 cpl->set_main_sound_sample_rate (48000);
1070 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1071 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1072 cpl->set_version_number (1);
1074 reel->add (simple_markers());
1078 dcp.set_annotation_text("hello");
1082 Editor e (find_cpl(dir));
1083 e.replace ("MainSound", "MainSoundX");
1086 check_verify_result (
1089 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1090 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1092 dcp::VerificationNote::Type::ERROR,
1093 dcp::VerificationNote::Code::INVALID_XML,
1094 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1095 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1096 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1097 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1098 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1099 "ExtensionMetadataList?,)'"),
1100 canonical(cpl->file().get()),
1103 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1108 /* DCP with invalid CompositionMetadataAsset */
1109 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1111 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1112 prepare_directory (dir);
1114 auto reel = make_shared<dcp::Reel>();
1115 reel->add (black_picture_asset(dir));
1116 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1118 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1119 cpl->set_main_sound_sample_rate (48000);
1120 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1121 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1125 dcp.set_annotation_text("hello");
1129 Editor e (find_cpl(dir));
1130 e.replace ("meta:Width", "meta:WidthX");
1133 check_verify_result (
1135 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1140 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1142 path const dir("build/test/verify_invalid_language1");
1143 prepare_directory (dir);
1144 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1145 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1146 asset->_language = "wrong-andbad";
1147 asset->write (dir / "subs.mxf");
1148 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1149 reel_asset->_language = "badlang";
1150 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1152 check_verify_result (
1155 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1157 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1162 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1163 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1165 path const dir("build/test/verify_invalid_language2");
1166 prepare_directory (dir);
1167 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1168 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1169 asset->_language = "wrong-andbad";
1170 asset->write (dir / "subs.mxf");
1171 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1172 reel_asset->_language = "badlang";
1173 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1175 check_verify_result (
1178 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1179 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1180 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1185 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1186 * the release territory.
1188 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1190 path const dir("build/test/verify_invalid_language3");
1191 prepare_directory (dir);
1193 auto picture = simple_picture (dir, "foo");
1194 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1195 auto reel = make_shared<dcp::Reel>();
1196 reel->add (reel_picture);
1197 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1198 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1199 reel->add (reel_sound);
1200 reel->add (simple_markers());
1202 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1204 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1205 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1206 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1207 cpl->set_main_sound_sample_rate (48000);
1208 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1209 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1210 cpl->set_version_number (1);
1211 cpl->_release_territory = "fred-jim";
1212 auto dcp = make_shared<dcp::DCP>(dir);
1214 dcp->set_annotation_text("hello");
1217 check_verify_result (
1220 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1221 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1222 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1223 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1229 vector<dcp::VerificationNote>
1230 check_picture_size (int width, int height, int frame_rate, bool three_d)
1232 using namespace boost::filesystem;
1234 path dcp_path = "build/test/verify_picture_test";
1235 prepare_directory (dcp_path);
1237 shared_ptr<dcp::PictureAsset> mp;
1239 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1241 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1243 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1245 auto image = black_image (dcp::Size(width, height));
1246 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1247 int const length = three_d ? frame_rate * 2 : frame_rate;
1248 for (int i = 0; i < length; ++i) {
1249 picture_writer->write (j2c.data(), j2c.size());
1251 picture_writer->finalize ();
1253 auto d = make_shared<dcp::DCP>(dcp_path);
1254 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1255 cpl->set_annotation_text ("A Test DCP");
1256 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1257 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1258 cpl->set_main_sound_sample_rate (48000);
1259 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1260 cpl->set_main_picture_active_area(dcp::Size(width, height));
1261 cpl->set_version_number (1);
1263 auto reel = make_shared<dcp::Reel>();
1266 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1268 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1271 reel->add (simple_markers(frame_rate));
1276 d->set_annotation_text("A Test DCP");
1279 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1285 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1287 auto notes = check_picture_size(width, height, frame_rate, three_d);
1288 BOOST_CHECK_EQUAL (notes.size(), 0U);
1294 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1296 auto notes = check_picture_size(width, height, frame_rate, three_d);
1297 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1298 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1299 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1305 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1307 auto notes = check_picture_size(width, height, frame_rate, three_d);
1308 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1309 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1310 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1316 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1318 auto notes = check_picture_size(width, height, frame_rate, three_d);
1319 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1320 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1321 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1325 BOOST_AUTO_TEST_CASE (verify_picture_size)
1327 using namespace boost::filesystem;
1330 check_picture_size_ok (2048, 858, 24, false);
1331 check_picture_size_ok (2048, 858, 25, false);
1332 check_picture_size_ok (2048, 858, 48, false);
1333 check_picture_size_ok (2048, 858, 24, true);
1334 check_picture_size_ok (2048, 858, 25, true);
1335 check_picture_size_ok (2048, 858, 48, true);
1338 check_picture_size_ok (1998, 1080, 24, false);
1339 check_picture_size_ok (1998, 1080, 25, false);
1340 check_picture_size_ok (1998, 1080, 48, false);
1341 check_picture_size_ok (1998, 1080, 24, true);
1342 check_picture_size_ok (1998, 1080, 25, true);
1343 check_picture_size_ok (1998, 1080, 48, true);
1346 check_picture_size_ok (4096, 1716, 24, false);
1349 check_picture_size_ok (3996, 2160, 24, false);
1351 /* Bad frame size */
1352 check_picture_size_bad_frame_size (2050, 858, 24, false);
1353 check_picture_size_bad_frame_size (2048, 658, 25, false);
1354 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1355 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1357 /* Bad 2K frame rate */
1358 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1359 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1360 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1362 /* Bad 4K frame rate */
1363 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1364 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1367 auto notes = check_picture_size(3996, 2160, 24, true);
1368 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1369 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1370 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1376 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")
1379 std::make_shared<dcp::SubtitleString>(
1387 dcp::Time(start_frame, 24, 24),
1388 dcp::Time(end_frame, 24, 24),
1390 dcp::HAlign::CENTER,
1394 dcp::Direction::LTR,
1406 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1408 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1409 prepare_directory (dir);
1411 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1412 for (int i = 0; i < 2048; ++i) {
1413 add_test_subtitle (asset, i * 24, i * 24 + 20);
1415 asset->set_language (dcp::LanguageTag("de-DE"));
1416 asset->write (dir / "subs.mxf");
1417 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1418 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1420 check_verify_result (
1423 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1425 dcp::VerificationNote::Type::BV21_ERROR,
1426 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1428 canonical(dir / "subs.mxf")
1430 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1431 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1437 shared_ptr<dcp::SMPTESubtitleAsset>
1438 make_large_subtitle_asset (path font_file)
1440 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1441 dcp::ArrayData big_fake_font(1024 * 1024);
1442 big_fake_font.write (font_file);
1443 for (int i = 0; i < 116; ++i) {
1444 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1452 verify_timed_text_asset_too_large (string name)
1454 auto const dir = path("build/test") / name;
1455 prepare_directory (dir);
1456 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1457 add_test_subtitle (asset, 0, 240);
1458 asset->set_language (dcp::LanguageTag("de-DE"));
1459 asset->write (dir / "subs.mxf");
1461 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1462 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1464 check_verify_result (
1467 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1468 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1470 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1471 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1476 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1478 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1479 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1483 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1485 path dir = "build/test/verify_missing_subtitle_language";
1486 prepare_directory (dir);
1487 auto dcp = make_simple (dir, 1, 106);
1490 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1491 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1492 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1493 "<ContentTitleText>Content</ContentTitleText>"
1494 "<AnnotationText>Annotation</AnnotationText>"
1495 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1496 "<ReelNumber>1</ReelNumber>"
1497 "<EditRate>24 1</EditRate>"
1498 "<TimeCodeRate>24</TimeCodeRate>"
1499 "<StartTime>00:00:00:00</StartTime>"
1500 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1502 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1503 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1504 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1510 dcp::File xml_file(dir / "subs.xml", "w");
1511 BOOST_REQUIRE (xml_file);
1512 xml_file.write(xml.c_str(), xml.size(), 1);
1514 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1515 subs->write (dir / "subs.mxf");
1517 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1518 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1519 dcp->set_annotation_text("A Test DCP");
1522 check_verify_result (
1525 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1526 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1531 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1533 path path ("build/test/verify_mismatched_subtitle_languages");
1534 auto constexpr reel_length = 192;
1535 auto dcp = make_simple (path, 2, reel_length);
1536 auto cpl = dcp->cpls()[0];
1539 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1540 subs->set_language (dcp::LanguageTag("de-DE"));
1541 subs->add (simple_subtitle());
1542 subs->write (path / "subs1.mxf");
1543 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1544 cpl->reels()[0]->add(reel_subs);
1548 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1549 subs->set_language (dcp::LanguageTag("en-US"));
1550 subs->add (simple_subtitle());
1551 subs->write (path / "subs2.mxf");
1552 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1553 cpl->reels()[1]->add(reel_subs);
1556 dcp->set_annotation_text("A Test DCP");
1559 check_verify_result (
1562 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1563 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1564 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1569 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1571 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1572 auto constexpr reel_length = 192;
1573 auto dcp = make_simple (path, 2, reel_length);
1574 auto cpl = dcp->cpls()[0];
1577 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1578 ccaps->set_language (dcp::LanguageTag("de-DE"));
1579 ccaps->add (simple_subtitle());
1580 ccaps->write (path / "subs1.mxf");
1581 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1582 cpl->reels()[0]->add(reel_ccaps);
1586 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1587 ccaps->set_language (dcp::LanguageTag("en-US"));
1588 ccaps->add (simple_subtitle());
1589 ccaps->write (path / "subs2.mxf");
1590 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1591 cpl->reels()[1]->add(reel_ccaps);
1594 dcp->set_annotation_text("A Test DCP");
1597 check_verify_result (
1600 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1601 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1606 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1608 path dir = "build/test/verify_missing_subtitle_start_time";
1609 prepare_directory (dir);
1610 auto dcp = make_simple (dir, 1, 106);
1613 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1614 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1615 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1616 "<ContentTitleText>Content</ContentTitleText>"
1617 "<AnnotationText>Annotation</AnnotationText>"
1618 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1619 "<ReelNumber>1</ReelNumber>"
1620 "<Language>de-DE</Language>"
1621 "<EditRate>24 1</EditRate>"
1622 "<TimeCodeRate>24</TimeCodeRate>"
1623 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1625 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1626 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1627 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1633 dcp::File xml_file(dir / "subs.xml", "w");
1634 BOOST_REQUIRE (xml_file);
1635 xml_file.write(xml.c_str(), xml.size(), 1);
1637 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1638 subs->write (dir / "subs.mxf");
1640 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1641 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1642 dcp->set_annotation_text("A Test DCP");
1645 check_verify_result (
1648 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1649 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1654 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1656 path dir = "build/test/verify_invalid_subtitle_start_time";
1657 prepare_directory (dir);
1658 auto dcp = make_simple (dir, 1, 106);
1661 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1662 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1663 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1664 "<ContentTitleText>Content</ContentTitleText>"
1665 "<AnnotationText>Annotation</AnnotationText>"
1666 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1667 "<ReelNumber>1</ReelNumber>"
1668 "<Language>de-DE</Language>"
1669 "<EditRate>24 1</EditRate>"
1670 "<TimeCodeRate>24</TimeCodeRate>"
1671 "<StartTime>00:00:02:00</StartTime>"
1672 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1674 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1675 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1676 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1682 dcp::File xml_file(dir / "subs.xml", "w");
1683 BOOST_REQUIRE (xml_file);
1684 xml_file.write(xml.c_str(), xml.size(), 1);
1686 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1687 subs->write (dir / "subs.mxf");
1689 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1690 dcp->cpls().front()->reels().front()->add(reel_subs);
1691 dcp->set_annotation_text("A Test DCP");
1694 check_verify_result (
1697 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1698 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1706 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1709 , v_position(v_position_)
1717 dcp::VAlign v_align;
1723 shared_ptr<dcp::CPL>
1724 dcp_with_text (path dir, vector<TestText> subs)
1726 prepare_directory (dir);
1727 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1728 asset->set_start_time (dcp::Time());
1729 for (auto i: subs) {
1730 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1732 asset->set_language (dcp::LanguageTag("de-DE"));
1733 asset->write (dir / "subs.mxf");
1735 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1736 return write_dcp_with_single_asset (dir, reel_asset);
1741 shared_ptr<dcp::CPL>
1742 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1744 prepare_directory (dir);
1745 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1746 asset->set_start_time (dcp::Time());
1747 asset->set_language (dcp::LanguageTag("de-DE"));
1749 auto subs_mxf = dir / "subs.mxf";
1750 asset->write (subs_mxf);
1752 /* The call to write() puts the asset into the DCP correctly but it will have
1753 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1756 ASDCP::TimedText::MXFWriter writer;
1757 ASDCP::WriterInfo writer_info;
1758 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1760 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1761 DCP_ASSERT (c == Kumu::UUID_Length);
1762 ASDCP::TimedText::TimedTextDescriptor descriptor;
1763 descriptor.ContainerDuration = asset->intrinsic_duration();
1764 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1765 DCP_ASSERT (c == Kumu::UUID_Length);
1766 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1767 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1768 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1769 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1772 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1773 return write_dcp_with_single_asset (dir, reel_asset);
1777 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1779 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1780 /* Just too early */
1781 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1782 check_verify_result (
1785 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1792 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1794 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1795 /* Just late enough */
1796 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1797 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1801 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1803 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1804 prepare_directory (dir);
1806 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1807 asset1->set_start_time (dcp::Time());
1808 /* Just late enough */
1809 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1810 asset1->set_language (dcp::LanguageTag("de-DE"));
1811 asset1->write (dir / "subs1.mxf");
1812 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1813 auto reel1 = make_shared<dcp::Reel>();
1814 reel1->add (reel_asset1);
1815 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1816 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1817 reel1->add (markers1);
1819 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1820 asset2->set_start_time (dcp::Time());
1821 /* This would be too early on first reel but should be OK on the second */
1822 add_test_subtitle (asset2, 3, 4 * 24);
1823 asset2->set_language (dcp::LanguageTag("de-DE"));
1824 asset2->write (dir / "subs2.mxf");
1825 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1826 auto reel2 = make_shared<dcp::Reel>();
1827 reel2->add (reel_asset2);
1828 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1829 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1830 reel2->add (markers2);
1832 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1835 auto dcp = make_shared<dcp::DCP>(dir);
1837 dcp->set_annotation_text("hello");
1840 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1844 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1846 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1847 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1851 { 5 * 24 + 1, 6 * 24 },
1853 check_verify_result (
1856 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1862 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1864 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1865 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1869 { 5 * 24 + 16, 8 * 24 },
1871 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1875 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1877 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1878 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1879 check_verify_result (
1882 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1888 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1890 auto const dir = path("build/test/verify_valid_subtitle_duration");
1891 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1892 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1896 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1898 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1899 prepare_directory (dir);
1900 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1901 asset->set_start_time (dcp::Time());
1902 add_test_subtitle (asset, 0, 4 * 24);
1903 asset->set_language (dcp::LanguageTag("de-DE"));
1904 asset->write (dir / "subs.mxf");
1906 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1907 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1908 check_verify_result (
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1912 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1913 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1920 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1922 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1923 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1926 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1927 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1928 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1929 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1931 check_verify_result (
1934 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1940 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1942 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1943 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1946 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1947 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1948 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1950 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1954 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1956 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1957 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1960 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1961 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1962 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1963 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1965 check_verify_result (
1968 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1974 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1976 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1977 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1980 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1981 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1982 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1983 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1985 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1989 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1991 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1992 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1995 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1997 check_verify_result (
2000 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
2001 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2006 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2008 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2009 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2012 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2014 check_verify_result (
2017 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2018 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2023 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2025 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2026 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2029 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2030 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2031 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2032 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2034 check_verify_result (
2037 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2038 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2043 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2045 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2046 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2049 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2050 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2051 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2053 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2057 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2059 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2060 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2063 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2064 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2065 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2066 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2068 check_verify_result (
2071 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2072 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2077 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2079 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2080 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2083 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2084 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2085 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2086 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2088 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2092 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2094 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2095 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2098 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2100 check_verify_result (
2103 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2108 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2110 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2111 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2114 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2116 check_verify_result (
2119 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2120 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2125 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2127 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2128 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2131 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2132 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2133 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2135 check_verify_result (
2138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2143 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2145 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2146 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2149 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2150 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2151 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2153 check_verify_result (
2156 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2157 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2162 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2164 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2165 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2168 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2169 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2170 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2172 check_verify_result (
2175 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2180 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2182 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2183 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2186 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2187 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2188 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2190 check_verify_result (
2193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2198 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2200 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2201 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2202 check_verify_result (
2205 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2206 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2211 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2213 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2214 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2215 check_verify_result (
2218 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2224 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2226 path const dir("build/test/verify_invalid_sound_frame_rate");
2227 prepare_directory (dir);
2229 auto picture = simple_picture (dir, "foo");
2230 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2231 auto reel = make_shared<dcp::Reel>();
2232 reel->add (reel_picture);
2233 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2234 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2235 reel->add (reel_sound);
2236 reel->add (simple_markers());
2237 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2239 auto dcp = make_shared<dcp::DCP>(dir);
2241 dcp->set_annotation_text("hello");
2244 check_verify_result (
2247 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2248 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2253 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2255 path const dir("build/test/verify_missing_cpl_annotation_text");
2256 auto dcp = make_simple (dir);
2257 dcp->set_annotation_text("A Test DCP");
2260 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2262 auto const cpl = dcp->cpls()[0];
2265 BOOST_REQUIRE (cpl->file());
2266 Editor e(cpl->file().get());
2267 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2270 check_verify_result (
2273 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2274 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2279 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2281 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2282 auto dcp = make_simple (dir);
2283 dcp->set_annotation_text("A Test DCP");
2286 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2287 auto const cpl = dcp->cpls()[0];
2290 BOOST_REQUIRE (cpl->file());
2291 Editor e(cpl->file().get());
2292 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2295 check_verify_result (
2298 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2299 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2304 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2306 path const dir("build/test/verify_mismatched_asset_duration");
2307 prepare_directory (dir);
2308 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2309 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2311 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2312 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2314 auto reel = make_shared<dcp::Reel>(
2315 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2316 make_shared<dcp::ReelSoundAsset>(ms, 0)
2319 reel->add (simple_markers());
2323 dcp->set_annotation_text("A Test DCP");
2326 check_verify_result (
2329 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2330 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2337 shared_ptr<dcp::CPL>
2338 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2340 prepare_directory (dir);
2341 auto dcp = make_shared<dcp::DCP>(dir);
2342 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2344 auto constexpr reel_length = 192;
2346 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2347 subs->set_language (dcp::LanguageTag("de-DE"));
2348 subs->set_start_time (dcp::Time());
2349 subs->add (simple_subtitle());
2350 subs->write (dir / "subs.mxf");
2351 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2353 auto reel1 = make_shared<dcp::Reel>(
2354 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2355 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2359 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2362 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2363 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2364 reel1->add (markers1);
2368 auto reel2 = make_shared<dcp::Reel>(
2369 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2370 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2374 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2377 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2378 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2379 reel2->add (markers2);
2384 dcp->set_annotation_text("A Test DCP");
2391 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2394 path dir ("build/test/missing_main_subtitle_from_some_reels");
2395 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2396 check_verify_result (
2399 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2400 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2406 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2407 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2408 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2412 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2413 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2414 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2420 shared_ptr<dcp::CPL>
2421 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2423 prepare_directory (dir);
2424 auto dcp = make_shared<dcp::DCP>(dir);
2425 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2427 auto constexpr reel_length = 192;
2429 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2430 subs->set_language (dcp::LanguageTag("de-DE"));
2431 subs->set_start_time (dcp::Time());
2432 subs->add (simple_subtitle());
2433 subs->write (dir / "subs.mxf");
2435 auto reel1 = make_shared<dcp::Reel>(
2436 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2437 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2440 for (int i = 0; i < caps_in_reel1; ++i) {
2441 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2444 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2445 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2446 reel1->add (markers1);
2450 auto reel2 = make_shared<dcp::Reel>(
2451 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2452 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2455 for (int i = 0; i < caps_in_reel2; ++i) {
2456 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2459 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2460 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2461 reel2->add (markers2);
2466 dcp->set_annotation_text("A Test DCP");
2473 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2476 path dir ("build/test/mismatched_closed_caption_asset_counts");
2477 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2478 check_verify_result (
2481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2482 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2487 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2488 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2489 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2493 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2494 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2495 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2502 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2504 prepare_directory (dir);
2505 auto dcp = make_shared<dcp::DCP>(dir);
2506 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2508 auto constexpr reel_length = 192;
2510 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2511 subs->set_language (dcp::LanguageTag("de-DE"));
2512 subs->set_start_time (dcp::Time());
2513 subs->add (simple_subtitle());
2514 subs->write (dir / "subs.mxf");
2515 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2518 auto reel = make_shared<dcp::Reel>(
2519 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2520 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2523 reel->add (reel_text);
2525 reel->add (simple_markers(reel_length));
2530 dcp->set_annotation_text("A Test DCP");
2533 check_verify_result (
2536 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2537 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2542 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2544 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2545 "build/test/verify_subtitle_entry_point_must_be_present",
2546 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2547 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2548 asset->unset_entry_point ();
2552 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2553 "build/test/verify_subtitle_entry_point_must_be_zero",
2554 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2555 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2556 asset->set_entry_point (4);
2560 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2561 "build/test/verify_closed_caption_entry_point_must_be_present",
2562 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2563 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2564 asset->unset_entry_point ();
2568 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2569 "build/test/verify_closed_caption_entry_point_must_be_zero",
2570 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2571 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2572 asset->set_entry_point (9);
2578 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2582 path const dir("build/test/verify_missing_hash");
2583 auto dcp = make_simple (dir);
2584 dcp->set_annotation_text("A Test DCP");
2587 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2588 auto const cpl = dcp->cpls()[0];
2589 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2590 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2591 auto asset_id = cpl->reels()[0]->main_picture()->id();
2594 BOOST_REQUIRE (cpl->file());
2595 Editor e(cpl->file().get());
2596 e.delete_first_line_containing("<Hash>");
2599 check_verify_result (
2602 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2603 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2610 verify_markers_test (
2612 vector<pair<dcp::Marker, dcp::Time>> markers,
2613 vector<dcp::VerificationNote> test_notes
2616 auto dcp = make_simple (dir);
2617 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2618 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2619 for (auto const& i: markers) {
2620 markers_asset->set (i.first, i.second);
2622 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2623 dcp->set_annotation_text("A Test DCP");
2626 check_verify_result ({dir}, test_notes);
2630 BOOST_AUTO_TEST_CASE (verify_markers)
2632 verify_markers_test (
2633 "build/test/verify_markers_all_correct",
2635 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2636 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2637 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2638 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2643 verify_markers_test (
2644 "build/test/verify_markers_missing_ffec",
2646 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2647 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2648 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2651 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2654 verify_markers_test (
2655 "build/test/verify_markers_missing_ffmc",
2657 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2658 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2659 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2665 verify_markers_test (
2666 "build/test/verify_markers_missing_ffoc",
2668 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2669 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2670 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2673 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2676 verify_markers_test (
2677 "build/test/verify_markers_missing_lfoc",
2679 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2680 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2681 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2684 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2687 verify_markers_test (
2688 "build/test/verify_markers_incorrect_ffoc",
2690 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2691 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2692 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2693 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2696 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2699 verify_markers_test (
2700 "build/test/verify_markers_incorrect_lfoc",
2702 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2703 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2704 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2705 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2708 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2713 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2715 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2716 prepare_directory (dir);
2717 auto dcp = make_simple (dir);
2718 auto cpl = dcp->cpls()[0];
2719 cpl->unset_version_number();
2720 dcp->set_annotation_text("A Test DCP");
2723 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2727 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2729 path dir = "build/test/verify_missing_extension_metadata1";
2730 auto dcp = make_simple (dir);
2731 dcp->set_annotation_text("A Test DCP");
2734 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2735 auto cpl = dcp->cpls()[0];
2738 Editor e (cpl->file().get());
2739 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2742 check_verify_result (
2745 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2746 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2751 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2753 path dir = "build/test/verify_missing_extension_metadata2";
2754 auto dcp = make_simple (dir);
2755 dcp->set_annotation_text("A Test DCP");
2758 auto cpl = dcp->cpls()[0];
2761 Editor e (cpl->file().get());
2762 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2765 check_verify_result (
2768 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2774 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2776 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2777 auto dcp = make_simple (dir);
2778 dcp->set_annotation_text("A Test DCP");
2781 auto const cpl = dcp->cpls()[0];
2784 Editor e (cpl->file().get());
2785 e.replace ("<meta:Name>A", "<meta:NameX>A");
2786 e.replace ("n</meta:Name>", "n</meta:NameX>");
2789 check_verify_result (
2792 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2793 { 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 },
2794 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2799 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2801 path dir = "build/test/verify_invalid_extension_metadata1";
2802 auto dcp = make_simple (dir);
2803 dcp->set_annotation_text("A Test DCP");
2806 auto cpl = dcp->cpls()[0];
2809 Editor e (cpl->file().get());
2810 e.replace ("Application", "Fred");
2813 check_verify_result (
2816 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2817 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2822 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2824 path dir = "build/test/verify_invalid_extension_metadata2";
2825 auto dcp = make_simple (dir);
2826 dcp->set_annotation_text("A Test DCP");
2829 auto cpl = dcp->cpls()[0];
2832 Editor e (cpl->file().get());
2833 e.replace ("DCP Constraints Profile", "Fred");
2836 check_verify_result (
2839 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2840 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2845 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2847 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2848 auto dcp = make_simple (dir);
2849 dcp->set_annotation_text("A Test DCP");
2852 auto const cpl = dcp->cpls()[0];
2855 Editor e (cpl->file().get());
2856 e.replace ("<meta:Value>", "<meta:ValueX>");
2857 e.replace ("</meta:Value>", "</meta:ValueX>");
2860 check_verify_result (
2863 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2864 { 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 },
2865 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2870 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2872 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2873 auto dcp = make_simple (dir);
2874 dcp->set_annotation_text("A Test DCP");
2877 auto const cpl = dcp->cpls()[0];
2880 Editor e (cpl->file().get());
2881 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2884 check_verify_result (
2887 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2888 { 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() },
2893 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2895 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2896 auto dcp = make_simple (dir);
2897 dcp->set_annotation_text("A Test DCP");
2900 auto const cpl = dcp->cpls()[0];
2903 Editor e (cpl->file().get());
2904 e.replace ("<meta:Property>", "<meta:PropertyX>");
2905 e.replace ("</meta:Property>", "</meta:PropertyX>");
2908 check_verify_result (
2911 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2912 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2913 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2918 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2920 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2921 auto dcp = make_simple (dir);
2922 dcp->set_annotation_text("A Test DCP");
2925 auto const cpl = dcp->cpls()[0];
2928 Editor e (cpl->file().get());
2929 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2930 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2933 check_verify_result (
2936 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2937 { 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 },
2938 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2944 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2946 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2947 prepare_directory (dir);
2948 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2949 copy_file (i.path(), dir / i.path().filename());
2952 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2953 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2957 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2960 check_verify_result (
2963 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2964 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2965 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2967 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2968 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2970 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2975 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2977 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2978 prepare_directory (dir);
2979 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2980 copy_file (i.path(), dir / i.path().filename());
2983 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2984 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2987 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2990 check_verify_result (
2993 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2994 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2996 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2997 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2998 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2999 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
3004 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3006 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3007 prepare_directory (dir);
3008 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3009 copy_file (i.path(), dir / i.path().filename());
3013 Editor e (dir / dcp_test1_pkl);
3014 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3017 check_verify_result ({dir}, {});
3021 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3023 path dir ("build/test/verify_must_not_be_partially_encrypted");
3024 prepare_directory (dir);
3028 auto signer = make_shared<dcp::CertificateChain>();
3029 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3030 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3031 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3032 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3034 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3038 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3041 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3042 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3043 for (int i = 0; i < 24; ++i) {
3044 writer->write (j2c.data(), j2c.size());
3046 writer->finalize ();
3048 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3050 auto reel = make_shared<dcp::Reel>(
3051 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3052 make_shared<dcp::ReelSoundAsset>(ms, 0)
3055 reel->add (simple_markers());
3059 cpl->set_content_version (
3060 {"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"}
3062 cpl->set_annotation_text ("A Test DCP");
3063 cpl->set_issuer ("OpenDCP 0.0.25");
3064 cpl->set_creator ("OpenDCP 0.0.25");
3065 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3066 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3067 cpl->set_main_sound_sample_rate (48000);
3068 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3069 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3070 cpl->set_version_number (1);
3074 d.set_issuer("OpenDCP 0.0.25");
3075 d.set_creator("OpenDCP 0.0.25");
3076 d.set_issue_date("2012-07-17T04:45:18+00:00");
3077 d.set_annotation_text("A Test DCP");
3078 d.write_xml(signer);
3080 check_verify_result (
3083 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3088 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3090 vector<dcp::VerificationNote> notes;
3091 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"));
3092 auto reader = picture.start_read ();
3093 auto frame = reader->get_frame (0);
3094 verify_j2k(frame, 0, 24, notes);
3095 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3099 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3101 vector<dcp::VerificationNote> notes;
3102 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3103 auto reader = picture.start_read ();
3104 auto frame = reader->get_frame (0);
3105 verify_j2k(frame, 0, 24, notes);
3106 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3110 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3112 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3113 prepare_directory (dir);
3114 auto dcp = make_simple (dir);
3116 vector<dcp::VerificationNote> notes;
3117 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3118 auto reader = picture.start_read ();
3119 auto frame = reader->get_frame (0);
3120 verify_j2k(frame, 0, 24, notes);
3121 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3125 /** Check that ResourceID and the XML ID being different is spotted */
3126 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3128 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3129 prepare_directory (dir);
3131 ASDCP::WriterInfo writer_info;
3132 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3135 auto mxf_id = dcp::make_uuid ();
3136 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3137 BOOST_REQUIRE (c == Kumu::UUID_Length);
3139 auto resource_id = dcp::make_uuid ();
3140 ASDCP::TimedText::TimedTextDescriptor descriptor;
3141 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3142 DCP_ASSERT (c == Kumu::UUID_Length);
3144 auto xml_id = dcp::make_uuid ();
3145 ASDCP::TimedText::MXFWriter writer;
3146 auto subs_mxf = dir / "subs.mxf";
3147 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3148 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3149 writer.WriteTimedTextResource (dcp::String::compose(
3150 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3151 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3152 "<Id>urn:uuid:%1</Id>"
3153 "<ContentTitleText>Content</ContentTitleText>"
3154 "<AnnotationText>Annotation</AnnotationText>"
3155 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3156 "<ReelNumber>1</ReelNumber>"
3157 "<Language>en-US</Language>"
3158 "<EditRate>25 1</EditRate>"
3159 "<TimeCodeRate>25</TimeCodeRate>"
3160 "<StartTime>00:00:00:00</StartTime>"
3162 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3163 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3164 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3173 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3174 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3176 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3178 check_verify_result (
3181 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3182 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3183 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3189 /** Check that ResourceID and the MXF ID being the same is spotted */
3190 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3192 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3193 prepare_directory (dir);
3195 ASDCP::WriterInfo writer_info;
3196 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3199 auto mxf_id = dcp::make_uuid ();
3200 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3201 BOOST_REQUIRE (c == Kumu::UUID_Length);
3203 auto resource_id = mxf_id;
3204 ASDCP::TimedText::TimedTextDescriptor descriptor;
3205 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3206 DCP_ASSERT (c == Kumu::UUID_Length);
3208 auto xml_id = resource_id;
3209 ASDCP::TimedText::MXFWriter writer;
3210 auto subs_mxf = dir / "subs.mxf";
3211 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3212 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3213 writer.WriteTimedTextResource (dcp::String::compose(
3214 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3215 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3216 "<Id>urn:uuid:%1</Id>"
3217 "<ContentTitleText>Content</ContentTitleText>"
3218 "<AnnotationText>Annotation</AnnotationText>"
3219 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3220 "<ReelNumber>1</ReelNumber>"
3221 "<Language>en-US</Language>"
3222 "<EditRate>25 1</EditRate>"
3223 "<TimeCodeRate>25</TimeCodeRate>"
3224 "<StartTime>00:00:00:00</StartTime>"
3226 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3227 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3228 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3237 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3238 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3240 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3242 check_verify_result (
3245 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3246 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3247 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3248 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3249 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3254 /** Check a DCP with a 3D asset marked as 2D */
3255 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3257 check_verify_result (
3258 { private_test / "data" / "xm" },
3261 dcp::VerificationNote::Type::WARNING,
3262 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3265 dcp::VerificationNote::Type::BV21_ERROR,
3266 dcp::VerificationNote::Code::INVALID_STANDARD
3273 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3275 path dir = "build/test/verify_unexpected_things_in_main_markers";
3276 prepare_directory (dir);
3277 auto dcp = make_simple (dir, 1, 24);
3278 dcp->set_annotation_text("A Test DCP");
3282 Editor e (find_cpl(dir));
3284 " <IntrinsicDuration>24</IntrinsicDuration>",
3285 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3289 dcp::CPL cpl (find_cpl(dir));
3291 check_verify_result (
3294 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3295 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3296 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3301 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3303 path dir = "build/test/verify_invalid_content_kind";
3304 prepare_directory (dir);
3305 auto dcp = make_simple (dir, 1, 24);
3306 dcp->set_annotation_text("A Test DCP");
3310 Editor e(find_cpl(dir));
3311 e.replace("trailer", "trip");
3314 dcp::CPL cpl (find_cpl(dir));
3316 check_verify_result (
3319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3320 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3326 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3328 path dir = "build/test/verify_valid_content_kind";
3329 prepare_directory (dir);
3330 auto dcp = make_simple (dir, 1, 24);
3331 dcp->set_annotation_text("A Test DCP");
3335 Editor e(find_cpl(dir));
3336 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3339 dcp::CPL cpl (find_cpl(dir));
3341 check_verify_result (
3344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3350 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3352 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3353 prepare_directory(dir);
3354 auto dcp = make_simple(dir, 1, 24);
3357 auto constexpr area = "<meta:MainPictureActiveArea>";
3360 Editor e(find_cpl(dir));
3361 e.delete_lines_after(area, 2);
3362 e.insert(area, "<meta:Height>4080</meta:Height>");
3363 e.insert(area, "<meta:Width>1997</meta:Width>");
3366 dcp::PKL pkl(find_pkl(dir));
3367 dcp::CPL cpl(find_cpl(dir));
3369 check_verify_result(
3372 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3373 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3374 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3375 { 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)) },
3380 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3382 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3383 prepare_directory(dir);
3384 auto dcp = make_simple(dir, 1, 24);
3387 auto constexpr area = "<meta:MainPictureActiveArea>";
3390 Editor e(find_cpl(dir));
3391 e.delete_lines_after(area, 2);
3392 e.insert(area, "<meta:Height>5125</meta:Height>");
3393 e.insert(area, "<meta:Width>9900</meta:Width>");
3396 dcp::PKL pkl(find_pkl(dir));
3397 dcp::CPL cpl(find_cpl(dir));
3399 check_verify_result(
3402 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3403 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3404 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3405 { 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)) },
3406 { 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)) },
3411 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3415 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3416 prepare_directory(dir);
3417 auto dcp = make_simple(dir, 1, 24);
3421 Editor e(find_pkl(dir));
3422 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3425 dcp::PKL pkl(find_pkl(dir));
3427 check_verify_result(
3430 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3435 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3439 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3440 prepare_directory(dir);
3441 auto dcp = make_simple(dir, 1, 24);
3445 Editor e(find_asset_map(dir));
3446 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3449 dcp::PKL pkl(find_pkl(dir));
3450 dcp::AssetMap asset_map(find_asset_map(dir));
3452 check_verify_result(
3455 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3456 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3457 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3462 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3464 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3466 dcp::MXFMetadata mxf_meta;
3467 mxf_meta.company_name = "OpenDCP";
3468 mxf_meta.product_name = "OpenDCP";
3469 mxf_meta.product_version = "0.0.25";
3471 auto constexpr sample_rate = 48000;
3472 auto constexpr frames = 240;
3474 boost::filesystem::remove_all(path);
3475 boost::filesystem::create_directories(path);
3476 auto dcp = make_shared<dcp::DCP>(path);
3477 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3478 cpl->set_annotation_text("hello");
3479 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3480 cpl->set_main_sound_sample_rate(sample_rate);
3481 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3482 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3483 cpl->set_version_number(1);
3487 /* Reel with 2 channels of audio */
3489 auto mp = simple_picture(path, "1", frames, {});
3490 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3492 auto reel = make_shared<dcp::Reel>(
3493 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3494 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3497 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3498 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3505 /* Reel with 6 channels of audio */
3507 auto mp = simple_picture(path, "2", frames, {});
3508 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3510 auto reel = make_shared<dcp::Reel>(
3511 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3512 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3515 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3516 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3523 dcp->set_annotation_text("hello");
3526 check_verify_result(
3529 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3534 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3536 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3538 dcp::MXFMetadata mxf_meta;
3539 mxf_meta.company_name = "OpenDCP";
3540 mxf_meta.product_name = "OpenDCP";
3541 mxf_meta.product_version = "0.0.25";
3543 auto constexpr sample_rate = 48000;
3544 auto constexpr frames = 240;
3546 boost::filesystem::remove_all(path);
3547 boost::filesystem::create_directories(path);
3548 auto dcp = make_shared<dcp::DCP>(path);
3549 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3550 cpl->set_annotation_text("hello");
3551 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3552 cpl->set_main_sound_sample_rate(sample_rate);
3553 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3554 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3555 cpl->set_version_number(1);
3557 auto mp = simple_picture(path, "1", frames, {});
3558 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3560 auto reel = make_shared<dcp::Reel>(
3561 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3562 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3565 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3566 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3567 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3573 dcp->set_annotation_text("hello");
3576 check_verify_result(
3579 { 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)) },
3584 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3586 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3587 auto constexpr video_frames = 24;
3588 auto constexpr sample_rate = 48000;
3590 boost::filesystem::remove_all(path);
3591 boost::filesystem::create_directories(path);
3593 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3594 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3596 dcp::Size const size(1998, 1080);
3597 auto image = make_shared<dcp::OpenJPEGImage>(size);
3598 boost::random::mt19937 rng(1);
3599 boost::random::uniform_int_distribution<> dist(0, 4095);
3600 for (int c = 0; c < 3; ++c) {
3601 for (int p = 0; p < (1998 * 1080); ++p) {
3602 image->data(c)[p] = dist(rng);
3605 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3606 for (int i = 0; i < 24; ++i) {
3607 picture_writer->write(j2c.data(), j2c.size());
3609 picture_writer->finalize();
3611 auto dcp = make_shared<dcp::DCP>(path);
3612 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3613 cpl->set_content_version(
3614 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3616 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3617 cpl->set_main_sound_sample_rate(sample_rate);
3618 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3619 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3620 cpl->set_version_number(1);
3622 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3624 auto reel = make_shared<dcp::Reel>(
3625 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3626 make_shared<dcp::ReelSoundAsset>(ms, 0)
3631 dcp->set_annotation_text("A Test DCP");
3634 check_verify_result(
3637 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3638 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3639 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3640 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3645 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3647 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3648 check_verify_result(
3651 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3652 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3653 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3654 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3656 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }