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);
364 add_font(shared_ptr<dcp::SubtitleAsset> asset)
366 dcp::ArrayData fake_font(1024);
367 asset->add_font("font", fake_font);
371 BOOST_AUTO_TEST_CASE (verify_no_error)
374 auto dir = setup (1, "no_error");
375 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
377 path const cpl_file = dir / dcp_test1_cpl;
378 path const pkl_file = dir / dcp_test1_pkl;
379 path const assetmap_file = dir / "ASSETMAP.xml";
381 auto st = stages.begin();
382 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
383 BOOST_REQUIRE (st->second);
384 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
386 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
387 BOOST_REQUIRE (st->second);
388 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
390 BOOST_CHECK_EQUAL (st->first, "Checking reel");
391 BOOST_REQUIRE (!st->second);
393 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
394 BOOST_REQUIRE (st->second);
395 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
397 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
398 BOOST_REQUIRE (st->second);
399 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
401 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
402 BOOST_REQUIRE (st->second);
403 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
405 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
406 BOOST_REQUIRE (st->second);
407 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
409 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
410 BOOST_REQUIRE (st->second);
411 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
413 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
414 BOOST_REQUIRE (st->second);
415 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
417 BOOST_REQUIRE (st == stages.end());
419 BOOST_CHECK_EQUAL (notes.size(), 0U);
423 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
425 using namespace boost::filesystem;
427 auto dir = setup (1, "incorrect_picture_sound_hash");
429 auto video_path = path(dir / "video.mxf");
430 auto mod = fopen(video_path.string().c_str(), "r+b");
432 fseek (mod, 4096, SEEK_SET);
434 fwrite (&x, sizeof(x), 1, mod);
437 auto audio_path = path(dir / "audio.mxf");
438 mod = fopen(audio_path.string().c_str(), "r+b");
440 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
441 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
444 dcp::ASDCPErrorSuspender sus;
445 check_verify_result (
448 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
449 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
454 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
456 using namespace boost::filesystem;
458 auto dir = setup (1, "mismatched_picture_sound_hashes");
461 Editor e (dir / dcp_test1_pkl);
462 e.replace ("<Hash>", "<Hash>x");
465 check_verify_result (
468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
469 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
470 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
471 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 28 },
472 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
473 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 20 },
478 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
480 auto dir = setup (1, "failed_read_content_kind");
483 Editor e (dir / dcp_test1_cpl);
484 e.replace ("<ContentKind>", "<ContentKind>x");
487 check_verify_result (
490 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
491 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
500 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
508 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
514 asset_map (string suffix)
516 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
520 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
522 check_verify_result_after_replace (
523 "invalid_picture_frame_rate", &cpl,
524 "<FrameRate>24 1", "<FrameRate>99 1",
525 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
526 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
530 BOOST_AUTO_TEST_CASE (verify_missing_asset)
532 auto dir = setup (1, "missing_asset");
533 remove (dir / "video.mxf");
534 check_verify_result (
537 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
542 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
544 check_verify_result_after_replace (
545 "empty_asset_path", &asset_map,
546 "<Path>video.mxf</Path>", "<Path></Path>",
547 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
552 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
554 check_verify_result_after_replace (
555 "mismatched_standard", &cpl,
556 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
557 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
558 dcp::VerificationNote::Code::INVALID_XML,
559 dcp::VerificationNote::Code::INVALID_XML,
560 dcp::VerificationNote::Code::INVALID_XML,
561 dcp::VerificationNote::Code::INVALID_XML,
562 dcp::VerificationNote::Code::INVALID_XML,
563 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
568 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
570 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
571 check_verify_result_after_replace (
572 "invalid_xml_cpl_id", &cpl,
573 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
574 { dcp::VerificationNote::Code::INVALID_XML }
579 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
581 check_verify_result_after_replace (
582 "invalid_xml_issue_date", &cpl,
583 "<IssueDate>", "<IssueDate>x",
584 { dcp::VerificationNote::Code::INVALID_XML,
585 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
590 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
592 check_verify_result_after_replace (
593 "invalid_xml_pkl_id", &pkl,
594 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
595 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
596 { dcp::VerificationNote::Code::INVALID_XML }
601 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
603 check_verify_result_after_replace (
604 "invalid_xml_asset_map_id", &asset_map,
605 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
606 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
607 { dcp::VerificationNote::Code::INVALID_XML }
612 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
615 auto dir = setup (3, "verify_invalid_standard");
616 auto notes = dcp::verify({dir}, &stage, &progress, {}, xsd_test);
618 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
619 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
620 path const assetmap_file = dir / "ASSETMAP";
622 auto st = stages.begin();
623 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
624 BOOST_REQUIRE (st->second);
625 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
627 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
628 BOOST_REQUIRE (st->second);
629 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
631 BOOST_CHECK_EQUAL (st->first, "Checking reel");
632 BOOST_REQUIRE (!st->second);
634 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
635 BOOST_REQUIRE (st->second);
636 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
638 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
639 BOOST_REQUIRE (st->second);
640 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
642 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
643 BOOST_REQUIRE (st->second);
644 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
646 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
647 BOOST_REQUIRE (st->second);
648 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
650 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
651 BOOST_REQUIRE (st->second);
652 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
654 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
655 BOOST_REQUIRE (st->second);
656 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
658 BOOST_REQUIRE (st == stages.end());
660 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
661 auto i = notes.begin ();
662 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
663 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
665 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
666 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
669 /* DCP with a short asset */
670 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
672 auto dir = setup (8, "invalid_duration");
673 check_verify_result (
676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
677 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
678 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
679 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
680 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
681 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
688 dcp_from_frame (dcp::ArrayData const& frame, path dir)
690 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
691 create_directories (dir);
692 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
693 for (int i = 0; i < 24; ++i) {
694 writer->write (frame.data(), frame.size());
698 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
699 return write_dcp_with_single_asset (dir, reel_asset);
703 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
705 int const too_big = 1302083 * 2;
707 /* Compress a black image */
708 auto image = black_image ();
709 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
710 BOOST_REQUIRE (frame.size() < too_big);
712 /* Place it in a bigger block with some zero padding at the end */
713 dcp::ArrayData oversized_frame(too_big);
714 memcpy (oversized_frame.data(), frame.data(), frame.size());
715 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
717 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
718 prepare_directory (dir);
719 auto cpl = dcp_from_frame (oversized_frame, dir);
721 check_verify_result (
724 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
725 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
731 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
733 int const nearly_too_big = 1302083 * 0.98;
735 /* Compress a black image */
736 auto image = black_image ();
737 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
738 BOOST_REQUIRE (frame.size() < nearly_too_big);
740 /* Place it in a bigger block with some zero padding at the end */
741 dcp::ArrayData oversized_frame(nearly_too_big);
742 memcpy (oversized_frame.data(), frame.data(), frame.size());
743 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
745 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
746 prepare_directory (dir);
747 auto cpl = dcp_from_frame (oversized_frame, dir);
749 check_verify_result (
752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
753 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
754 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
759 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
761 /* Compress a black image */
762 auto image = black_image ();
763 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
764 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
766 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
767 prepare_directory (dir);
768 auto cpl = dcp_from_frame (frame, dir);
770 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
774 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
776 path const dir("build/test/verify_valid_interop_subtitles");
777 prepare_directory (dir);
778 copy_file ("test/data/subs1.xml", dir / "subs.xml");
779 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
780 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
781 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
783 check_verify_result (
785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
786 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
791 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
793 using namespace boost::filesystem;
795 path const dir("build/test/verify_invalid_interop_subtitles");
796 prepare_directory (dir);
797 copy_file ("test/data/subs1.xml", dir / "subs.xml");
798 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
799 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
800 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
803 Editor e (dir / "subs.xml");
804 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
807 check_verify_result (
810 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
811 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
813 dcp::VerificationNote::Type::ERROR,
814 dcp::VerificationNote::Code::INVALID_XML,
815 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
819 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
824 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_no_subtitles)
826 path const dir("build/test/verify_interop_subtitle_asset_with_no_subtitles");
827 prepare_directory(dir);
828 copy_file("test/data/subs4.xml", dir / "subs.xml");
829 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
830 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
831 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
833 check_verify_result (
836 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
838 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"theFontId"} }
844 BOOST_AUTO_TEST_CASE(verify_interop_subtitle_asset_with_single_space_subtitle)
846 path const dir("build/test/verify_interop_subtitle_asset_with_single_space_subtitle");
847 prepare_directory(dir);
848 copy_file("test/data/subs5.xml", dir / "subs.xml");
849 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
850 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
851 write_dcp_with_single_asset(dir, reel_asset, dcp::Standard::INTEROP);
853 check_verify_result (
856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
857 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"Arial"} }
863 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
865 path const dir("build/test/verify_valid_smpte_subtitles");
866 prepare_directory (dir);
867 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
868 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
869 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
870 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
875 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
876 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-04-14T13:19:14.000+02:00"} },
877 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
882 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
884 using namespace boost::filesystem;
886 path const dir("build/test/verify_invalid_smpte_subtitles");
887 prepare_directory (dir);
888 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
889 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
890 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
891 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
892 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
894 check_verify_result (
897 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
899 dcp::VerificationNote::Type::ERROR,
900 dcp::VerificationNote::Code::INVALID_XML,
901 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
905 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
906 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
907 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2020-05-09T00:29:21.000+02:00"} },
908 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
913 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
915 path const dir("build/test/verify_empty_text_node_in_subtitles");
916 prepare_directory (dir);
917 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
918 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
919 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
920 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
922 check_verify_result (
925 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
926 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
928 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
929 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2021-08-09T18:34:46.000+02:00"} },
930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, asset->id() }
935 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
936 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
938 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
939 prepare_directory (dir);
940 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
941 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
942 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
943 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
945 check_verify_result (
948 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
949 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} }
954 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
955 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
957 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
958 prepare_directory (dir);
959 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
960 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
961 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
962 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
964 check_verify_result (
967 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE, asset->id(), boost::filesystem::canonical(asset->file().get()) },
968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
969 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
970 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_FONT, string{"font0"} },
975 BOOST_AUTO_TEST_CASE (verify_external_asset)
977 path const ov_dir("build/test/verify_external_asset");
978 prepare_directory (ov_dir);
980 auto image = black_image ();
981 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
982 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
983 dcp_from_frame (frame, ov_dir);
985 dcp::DCP ov (ov_dir);
988 path const vf_dir("build/test/verify_external_asset_vf");
989 prepare_directory (vf_dir);
991 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
992 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
994 check_verify_result (
997 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
998 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1003 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
1005 path const dir("build/test/verify_valid_cpl_metadata");
1006 prepare_directory (dir);
1008 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1009 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1010 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1012 auto reel = make_shared<dcp::Reel>();
1013 reel->add (reel_asset);
1015 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
1016 reel->add (simple_markers(16 * 24));
1018 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1020 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1021 cpl->set_main_sound_sample_rate (48000);
1022 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1023 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1024 cpl->set_version_number (1);
1028 dcp.set_annotation_text("hello");
1034 find_prefix(path dir, string prefix)
1036 auto iter = std::find_if(directory_iterator(dir), directory_iterator(), [prefix](path const& p) {
1037 return boost::starts_with(p.filename().string(), prefix);
1040 BOOST_REQUIRE(iter != directory_iterator());
1041 return iter->path();
1045 path find_cpl (path dir)
1047 return find_prefix(dir, "cpl_");
1054 return find_prefix(dir, "pkl_");
1059 find_asset_map(path dir)
1061 return find_prefix(dir, "ASSETMAP");
1065 /* DCP with invalid CompositionMetadataAsset */
1066 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
1068 using namespace boost::filesystem;
1070 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
1071 prepare_directory (dir);
1073 auto reel = make_shared<dcp::Reel>();
1074 reel->add (black_picture_asset(dir));
1075 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1077 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1078 cpl->set_main_sound_sample_rate (48000);
1079 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1080 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1081 cpl->set_version_number (1);
1083 reel->add (simple_markers());
1087 dcp.set_annotation_text("hello");
1091 Editor e (find_cpl(dir));
1092 e.replace ("MainSound", "MainSoundX");
1095 check_verify_result (
1098 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
1099 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
1101 dcp::VerificationNote::Type::ERROR,
1102 dcp::VerificationNote::Code::INVALID_XML,
1103 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1104 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1105 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1106 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1107 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1108 "ExtensionMetadataList?,)'"),
1109 canonical(cpl->file().get()),
1112 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1117 /* DCP with invalid CompositionMetadataAsset */
1118 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1120 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1121 prepare_directory (dir);
1123 auto reel = make_shared<dcp::Reel>();
1124 reel->add (black_picture_asset(dir));
1125 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1127 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1128 cpl->set_main_sound_sample_rate (48000);
1129 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1130 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1134 dcp.set_annotation_text("hello");
1138 Editor e (find_cpl(dir));
1139 e.replace ("meta:Width", "meta:WidthX");
1142 check_verify_result (
1144 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1149 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1151 path const dir("build/test/verify_invalid_language1");
1152 prepare_directory (dir);
1153 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1154 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1155 asset->_language = "wrong-andbad";
1156 asset->write (dir / "subs.mxf");
1157 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1158 reel_asset->_language = "badlang";
1159 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1161 check_verify_result (
1164 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1165 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1166 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1171 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1172 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1174 path const dir("build/test/verify_invalid_language2");
1175 prepare_directory (dir);
1176 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1177 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1178 asset->_language = "wrong-andbad";
1179 asset->write (dir / "subs.mxf");
1180 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1181 reel_asset->_language = "badlang";
1182 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1184 check_verify_result (
1187 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1189 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1194 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1195 * the release territory.
1197 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1199 path const dir("build/test/verify_invalid_language3");
1200 prepare_directory (dir);
1202 auto picture = simple_picture (dir, "foo");
1203 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1204 auto reel = make_shared<dcp::Reel>();
1205 reel->add (reel_picture);
1206 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1207 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1208 reel->add (reel_sound);
1209 reel->add (simple_markers());
1211 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1213 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1214 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1215 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1216 cpl->set_main_sound_sample_rate (48000);
1217 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1218 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1219 cpl->set_version_number (1);
1220 cpl->_release_territory = "fred-jim";
1221 auto dcp = make_shared<dcp::DCP>(dir);
1223 dcp->set_annotation_text("hello");
1226 check_verify_result (
1229 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1238 vector<dcp::VerificationNote>
1239 check_picture_size (int width, int height, int frame_rate, bool three_d)
1241 using namespace boost::filesystem;
1243 path dcp_path = "build/test/verify_picture_test";
1244 prepare_directory (dcp_path);
1246 shared_ptr<dcp::PictureAsset> mp;
1248 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1250 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1252 auto picture_writer = mp->start_write(dcp_path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
1254 auto image = black_image (dcp::Size(width, height));
1255 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1256 int const length = three_d ? frame_rate * 2 : frame_rate;
1257 for (int i = 0; i < length; ++i) {
1258 picture_writer->write (j2c.data(), j2c.size());
1260 picture_writer->finalize ();
1262 auto d = make_shared<dcp::DCP>(dcp_path);
1263 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1264 cpl->set_annotation_text ("A Test DCP");
1265 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1266 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
1267 cpl->set_main_sound_sample_rate (48000);
1268 cpl->set_main_picture_stored_area(dcp::Size(width, height));
1269 cpl->set_main_picture_active_area(dcp::Size(width, height));
1270 cpl->set_version_number (1);
1272 auto reel = make_shared<dcp::Reel>();
1275 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1277 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1280 reel->add (simple_markers(frame_rate));
1285 d->set_annotation_text("A Test DCP");
1288 return dcp::verify({dcp_path}, &stage, &progress, {}, xsd_test);
1294 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1296 auto notes = check_picture_size(width, height, frame_rate, three_d);
1297 BOOST_CHECK_EQUAL (notes.size(), 0U);
1303 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1305 auto notes = check_picture_size(width, height, frame_rate, three_d);
1306 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1307 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1308 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1314 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1316 auto notes = check_picture_size(width, height, frame_rate, three_d);
1317 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1318 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1319 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1325 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1327 auto notes = check_picture_size(width, height, frame_rate, three_d);
1328 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1329 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1330 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1334 BOOST_AUTO_TEST_CASE (verify_picture_size)
1336 using namespace boost::filesystem;
1339 check_picture_size_ok (2048, 858, 24, false);
1340 check_picture_size_ok (2048, 858, 25, false);
1341 check_picture_size_ok (2048, 858, 48, false);
1342 check_picture_size_ok (2048, 858, 24, true);
1343 check_picture_size_ok (2048, 858, 25, true);
1344 check_picture_size_ok (2048, 858, 48, true);
1347 check_picture_size_ok (1998, 1080, 24, false);
1348 check_picture_size_ok (1998, 1080, 25, false);
1349 check_picture_size_ok (1998, 1080, 48, false);
1350 check_picture_size_ok (1998, 1080, 24, true);
1351 check_picture_size_ok (1998, 1080, 25, true);
1352 check_picture_size_ok (1998, 1080, 48, true);
1355 check_picture_size_ok (4096, 1716, 24, false);
1358 check_picture_size_ok (3996, 2160, 24, false);
1360 /* Bad frame size */
1361 check_picture_size_bad_frame_size (2050, 858, 24, false);
1362 check_picture_size_bad_frame_size (2048, 658, 25, false);
1363 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1364 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1366 /* Bad 2K frame rate */
1367 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1368 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1369 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1371 /* Bad 4K frame rate */
1372 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1373 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1376 auto notes = check_picture_size(3996, 2160, 24, true);
1377 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1378 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1379 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1385 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")
1388 std::make_shared<dcp::SubtitleString>(
1396 dcp::Time(start_frame, 24, 24),
1397 dcp::Time(end_frame, 24, 24),
1399 dcp::HAlign::CENTER,
1403 dcp::Direction::LTR,
1415 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1417 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1418 prepare_directory (dir);
1420 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1421 for (int i = 0; i < 2048; ++i) {
1422 add_test_subtitle (asset, i * 24, i * 24 + 20);
1425 asset->set_language (dcp::LanguageTag("de-DE"));
1426 asset->write (dir / "subs.mxf");
1427 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1428 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1430 check_verify_result (
1433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1435 dcp::VerificationNote::Type::BV21_ERROR,
1436 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1438 canonical(dir / "subs.mxf")
1440 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1441 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1447 shared_ptr<dcp::SMPTESubtitleAsset>
1448 make_large_subtitle_asset (path font_file)
1450 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1451 dcp::ArrayData big_fake_font(1024 * 1024);
1452 big_fake_font.write (font_file);
1453 for (int i = 0; i < 116; ++i) {
1454 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1462 verify_timed_text_asset_too_large (string name)
1464 auto const dir = path("build/test") / name;
1465 prepare_directory (dir);
1466 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1467 add_test_subtitle (asset, 0, 240);
1468 asset->set_language (dcp::LanguageTag("de-DE"));
1469 asset->write (dir / "subs.mxf");
1471 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1472 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1474 check_verify_result (
1477 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695488"), canonical(dir / "subs.mxf") },
1478 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1479 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1480 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1486 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1488 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1489 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1493 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1495 path dir = "build/test/verify_missing_subtitle_language";
1496 prepare_directory (dir);
1497 auto dcp = make_simple (dir, 1, 106);
1500 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1501 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1502 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1503 "<ContentTitleText>Content</ContentTitleText>"
1504 "<AnnotationText>Annotation</AnnotationText>"
1505 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1506 "<ReelNumber>1</ReelNumber>"
1507 "<EditRate>24 1</EditRate>"
1508 "<TimeCodeRate>24</TimeCodeRate>"
1509 "<StartTime>00:00:00:00</StartTime>"
1510 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1512 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1513 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1514 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1520 dcp::File xml_file(dir / "subs.xml", "w");
1521 BOOST_REQUIRE (xml_file);
1522 xml_file.write(xml.c_str(), xml.size(), 1);
1524 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1525 subs->write (dir / "subs.mxf");
1527 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1528 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1531 check_verify_result (
1534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1535 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1540 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1542 path path ("build/test/verify_mismatched_subtitle_languages");
1543 auto constexpr reel_length = 192;
1544 auto dcp = make_simple (path, 2, reel_length);
1545 auto cpl = dcp->cpls()[0];
1548 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1549 subs->set_language (dcp::LanguageTag("de-DE"));
1550 subs->add (simple_subtitle());
1552 subs->write (path / "subs1.mxf");
1553 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1554 cpl->reels()[0]->add(reel_subs);
1558 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1559 subs->set_language (dcp::LanguageTag("en-US"));
1560 subs->add (simple_subtitle());
1562 subs->write (path / "subs2.mxf");
1563 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1564 cpl->reels()[1]->add(reel_subs);
1569 check_verify_result (
1572 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1573 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1574 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1579 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1581 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1582 auto constexpr reel_length = 192;
1583 auto dcp = make_simple (path, 2, reel_length);
1584 auto cpl = dcp->cpls()[0];
1587 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1588 ccaps->set_language (dcp::LanguageTag("de-DE"));
1589 ccaps->add (simple_subtitle());
1591 ccaps->write (path / "subs1.mxf");
1592 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1593 cpl->reels()[0]->add(reel_ccaps);
1597 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1598 ccaps->set_language (dcp::LanguageTag("en-US"));
1599 ccaps->add (simple_subtitle());
1601 ccaps->write (path / "subs2.mxf");
1602 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1603 cpl->reels()[1]->add(reel_ccaps);
1608 check_verify_result (
1611 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1612 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1617 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1619 path dir = "build/test/verify_missing_subtitle_start_time";
1620 prepare_directory (dir);
1621 auto dcp = make_simple (dir, 1, 106);
1624 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1625 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1626 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1627 "<ContentTitleText>Content</ContentTitleText>"
1628 "<AnnotationText>Annotation</AnnotationText>"
1629 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1630 "<ReelNumber>1</ReelNumber>"
1631 "<Language>de-DE</Language>"
1632 "<EditRate>24 1</EditRate>"
1633 "<TimeCodeRate>24</TimeCodeRate>"
1634 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1636 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1637 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1638 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1644 dcp::File xml_file(dir / "subs.xml", "w");
1645 BOOST_REQUIRE (xml_file);
1646 xml_file.write(xml.c_str(), xml.size(), 1);
1648 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1649 subs->write (dir / "subs.mxf");
1651 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1652 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1655 check_verify_result (
1658 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1659 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1664 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1666 path dir = "build/test/verify_invalid_subtitle_start_time";
1667 prepare_directory (dir);
1668 auto dcp = make_simple (dir, 1, 106);
1671 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1672 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1673 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1674 "<ContentTitleText>Content</ContentTitleText>"
1675 "<AnnotationText>Annotation</AnnotationText>"
1676 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1677 "<ReelNumber>1</ReelNumber>"
1678 "<Language>de-DE</Language>"
1679 "<EditRate>24 1</EditRate>"
1680 "<TimeCodeRate>24</TimeCodeRate>"
1681 "<StartTime>00:00:02:00</StartTime>"
1682 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1684 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1685 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1686 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1692 dcp::File xml_file(dir / "subs.xml", "w");
1693 BOOST_REQUIRE (xml_file);
1694 xml_file.write(xml.c_str(), xml.size(), 1);
1696 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1697 subs->write (dir / "subs.mxf");
1699 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1700 dcp->cpls().front()->reels().front()->add(reel_subs);
1703 check_verify_result (
1706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1707 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1715 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1718 , v_position(v_position_)
1726 dcp::VAlign v_align;
1732 shared_ptr<dcp::CPL>
1733 dcp_with_text (path dir, vector<TestText> subs)
1735 prepare_directory (dir);
1736 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1737 asset->set_start_time (dcp::Time());
1738 for (auto i: subs) {
1739 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1741 asset->set_language (dcp::LanguageTag("de-DE"));
1743 asset->write (dir / "subs.mxf");
1745 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1746 return write_dcp_with_single_asset (dir, reel_asset);
1751 shared_ptr<dcp::CPL>
1752 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1754 prepare_directory (dir);
1755 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1756 asset->set_start_time (dcp::Time());
1757 asset->set_language (dcp::LanguageTag("de-DE"));
1759 auto subs_mxf = dir / "subs.mxf";
1760 asset->write (subs_mxf);
1762 /* The call to write() puts the asset into the DCP correctly but it will have
1763 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1766 ASDCP::TimedText::MXFWriter writer;
1767 ASDCP::WriterInfo writer_info;
1768 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1770 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1771 DCP_ASSERT (c == Kumu::UUID_Length);
1772 ASDCP::TimedText::TimedTextDescriptor descriptor;
1773 descriptor.ContainerDuration = asset->intrinsic_duration();
1774 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1775 DCP_ASSERT (c == Kumu::UUID_Length);
1776 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1777 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1778 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1779 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1782 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1783 return write_dcp_with_single_asset (dir, reel_asset);
1787 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1789 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1790 /* Just too early */
1791 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1792 check_verify_result (
1795 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1796 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1802 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1804 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1805 /* Just late enough */
1806 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1807 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1811 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1813 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1814 prepare_directory (dir);
1816 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1817 asset1->set_start_time (dcp::Time());
1818 /* Just late enough */
1819 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1820 asset1->set_language (dcp::LanguageTag("de-DE"));
1822 asset1->write (dir / "subs1.mxf");
1823 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1824 auto reel1 = make_shared<dcp::Reel>();
1825 reel1->add (reel_asset1);
1826 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1827 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1828 reel1->add (markers1);
1830 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1831 asset2->set_start_time (dcp::Time());
1833 /* This would be too early on first reel but should be OK on the second */
1834 add_test_subtitle (asset2, 3, 4 * 24);
1835 asset2->set_language (dcp::LanguageTag("de-DE"));
1836 asset2->write (dir / "subs2.mxf");
1837 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1838 auto reel2 = make_shared<dcp::Reel>();
1839 reel2->add (reel_asset2);
1840 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1841 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1842 reel2->add (markers2);
1844 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1847 auto dcp = make_shared<dcp::DCP>(dir);
1849 dcp->set_annotation_text("hello");
1852 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1856 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1858 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1859 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1863 { 5 * 24 + 1, 6 * 24 },
1865 check_verify_result (
1868 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1869 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1874 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1876 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1877 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1881 { 5 * 24 + 16, 8 * 24 },
1883 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1887 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1889 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1890 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1891 check_verify_result (
1894 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1895 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1900 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1902 auto const dir = path("build/test/verify_valid_subtitle_duration");
1903 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1904 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1908 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1910 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1911 prepare_directory (dir);
1912 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1913 asset->set_start_time (dcp::Time());
1914 add_test_subtitle (asset, 0, 4 * 24);
1916 asset->set_language (dcp::LanguageTag("de-DE"));
1917 asset->write (dir / "subs.mxf");
1919 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1920 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1921 check_verify_result (
1924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1925 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1926 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1933 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1935 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1936 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1939 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1940 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1941 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1942 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1944 check_verify_result (
1947 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1948 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1953 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1955 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1956 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1959 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1960 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1961 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1963 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1967 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1969 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1970 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1973 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1974 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1975 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1976 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1978 check_verify_result (
1981 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1987 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1989 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1990 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1993 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1994 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1995 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1996 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1998 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2002 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2004 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2005 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2008 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2010 check_verify_result (
2013 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
2014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2019 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2021 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2022 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2025 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2027 check_verify_result (
2030 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2031 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2036 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2038 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2039 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2042 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2043 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2044 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2045 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2047 check_verify_result (
2050 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2051 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2056 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2058 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2059 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2062 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2063 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2064 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2066 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2070 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2072 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2073 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2076 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2077 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2078 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2079 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2081 check_verify_result (
2084 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2085 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2090 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2092 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2093 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2096 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2097 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2098 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2099 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2101 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2105 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2107 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2108 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2111 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2113 check_verify_result (
2116 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2121 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2123 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2124 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2127 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2129 check_verify_result (
2132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2133 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2138 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2140 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2141 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2144 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2145 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2146 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2148 check_verify_result (
2151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2156 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2158 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2159 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2162 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2163 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2164 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2166 check_verify_result (
2169 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2170 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2175 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2177 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2178 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2181 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2182 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2183 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2185 check_verify_result (
2188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2193 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2195 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2196 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2199 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2200 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2201 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2203 check_verify_result (
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_ordering3)
2213 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2214 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2215 check_verify_result (
2218 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2219 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2224 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2226 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2227 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2228 check_verify_result (
2231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2237 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2239 path const dir("build/test/verify_invalid_sound_frame_rate");
2240 prepare_directory (dir);
2242 auto picture = simple_picture (dir, "foo");
2243 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2244 auto reel = make_shared<dcp::Reel>();
2245 reel->add (reel_picture);
2246 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2247 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2248 reel->add (reel_sound);
2249 reel->add (simple_markers());
2250 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2252 auto dcp = make_shared<dcp::DCP>(dir);
2254 dcp->set_annotation_text("hello");
2257 check_verify_result (
2260 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2261 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2266 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2268 path const dir("build/test/verify_missing_cpl_annotation_text");
2269 auto dcp = make_simple (dir);
2272 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2274 auto const cpl = dcp->cpls()[0];
2277 BOOST_REQUIRE (cpl->file());
2278 Editor e(cpl->file().get());
2279 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2282 check_verify_result (
2285 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2286 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2291 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2293 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2294 auto dcp = make_simple (dir);
2297 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2298 auto const cpl = dcp->cpls()[0];
2301 BOOST_REQUIRE (cpl->file());
2302 Editor e(cpl->file().get());
2303 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2306 check_verify_result (
2309 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2310 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2315 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2317 path const dir("build/test/verify_mismatched_asset_duration");
2318 prepare_directory (dir);
2319 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2320 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2322 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2323 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2325 auto reel = make_shared<dcp::Reel>(
2326 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2327 make_shared<dcp::ReelSoundAsset>(ms, 0)
2330 reel->add (simple_markers());
2334 dcp->set_annotation_text("A Test DCP");
2337 check_verify_result (
2340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2348 shared_ptr<dcp::CPL>
2349 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2351 prepare_directory (dir);
2352 auto dcp = make_shared<dcp::DCP>(dir);
2353 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2355 auto constexpr reel_length = 192;
2357 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2358 subs->set_language (dcp::LanguageTag("de-DE"));
2359 subs->set_start_time (dcp::Time());
2360 subs->add (simple_subtitle());
2362 subs->write (dir / "subs.mxf");
2363 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2365 auto reel1 = make_shared<dcp::Reel>(
2366 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2367 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2371 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2374 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2375 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2376 reel1->add (markers1);
2380 auto reel2 = make_shared<dcp::Reel>(
2381 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2382 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2386 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2389 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2390 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2391 reel2->add (markers2);
2396 dcp->set_annotation_text("A Test DCP");
2403 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2406 path dir ("build/test/missing_main_subtitle_from_some_reels");
2407 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2408 check_verify_result (
2411 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2412 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2418 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2419 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2420 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2424 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2425 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2426 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2432 shared_ptr<dcp::CPL>
2433 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2435 prepare_directory (dir);
2436 auto dcp = make_shared<dcp::DCP>(dir);
2437 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2439 auto constexpr reel_length = 192;
2441 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2442 subs->set_language (dcp::LanguageTag("de-DE"));
2443 subs->set_start_time (dcp::Time());
2444 subs->add (simple_subtitle());
2446 subs->write (dir / "subs.mxf");
2448 auto reel1 = make_shared<dcp::Reel>(
2449 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2450 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2453 for (int i = 0; i < caps_in_reel1; ++i) {
2454 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2457 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2458 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2459 reel1->add (markers1);
2463 auto reel2 = make_shared<dcp::Reel>(
2464 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2465 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2468 for (int i = 0; i < caps_in_reel2; ++i) {
2469 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2472 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2473 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2474 reel2->add (markers2);
2479 dcp->set_annotation_text("A Test DCP");
2486 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2489 path dir ("build/test/mismatched_closed_caption_asset_counts");
2490 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2491 check_verify_result (
2494 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2495 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2500 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2501 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2502 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2506 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2507 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2508 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2515 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2517 prepare_directory (dir);
2518 auto dcp = make_shared<dcp::DCP>(dir);
2519 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2521 auto constexpr reel_length = 192;
2523 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2524 subs->set_language (dcp::LanguageTag("de-DE"));
2525 subs->set_start_time (dcp::Time());
2526 subs->add (simple_subtitle());
2528 subs->write (dir / "subs.mxf");
2529 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2532 auto reel = make_shared<dcp::Reel>(
2533 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2534 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2537 reel->add (reel_text);
2539 reel->add (simple_markers(reel_length));
2544 dcp->set_annotation_text("A Test DCP");
2547 check_verify_result (
2550 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2556 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2558 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2559 "build/test/verify_subtitle_entry_point_must_be_present",
2560 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2561 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2562 asset->unset_entry_point ();
2566 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2567 "build/test/verify_subtitle_entry_point_must_be_zero",
2568 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2569 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2570 asset->set_entry_point (4);
2574 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2575 "build/test/verify_closed_caption_entry_point_must_be_present",
2576 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2577 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2578 asset->unset_entry_point ();
2582 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2583 "build/test/verify_closed_caption_entry_point_must_be_zero",
2584 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2585 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2586 asset->set_entry_point (9);
2592 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2596 path const dir("build/test/verify_missing_hash");
2597 auto dcp = make_simple (dir);
2600 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2601 auto const cpl = dcp->cpls()[0];
2602 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2603 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2604 auto asset_id = cpl->reels()[0]->main_picture()->id();
2607 BOOST_REQUIRE (cpl->file());
2608 Editor e(cpl->file().get());
2609 e.delete_first_line_containing("<Hash>");
2612 check_verify_result (
2615 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2616 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2623 verify_markers_test (
2625 vector<pair<dcp::Marker, dcp::Time>> markers,
2626 vector<dcp::VerificationNote> test_notes
2629 auto dcp = make_simple (dir);
2630 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2631 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2632 for (auto const& i: markers) {
2633 markers_asset->set (i.first, i.second);
2635 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2638 check_verify_result ({dir}, test_notes);
2642 BOOST_AUTO_TEST_CASE (verify_markers)
2644 verify_markers_test (
2645 "build/test/verify_markers_all_correct",
2647 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2648 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2649 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2650 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2655 verify_markers_test (
2656 "build/test/verify_markers_missing_ffec",
2658 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2659 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2660 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2663 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2666 verify_markers_test (
2667 "build/test/verify_markers_missing_ffmc",
2669 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2670 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2671 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2677 verify_markers_test (
2678 "build/test/verify_markers_missing_ffoc",
2680 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2681 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2682 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2685 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2688 verify_markers_test (
2689 "build/test/verify_markers_missing_lfoc",
2691 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2692 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2693 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2696 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2699 verify_markers_test (
2700 "build/test/verify_markers_incorrect_ffoc",
2702 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2703 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2704 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2705 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2708 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2711 verify_markers_test (
2712 "build/test/verify_markers_incorrect_lfoc",
2714 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2715 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2716 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2717 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2720 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2725 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2727 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2728 prepare_directory (dir);
2729 auto dcp = make_simple (dir);
2730 auto cpl = dcp->cpls()[0];
2731 cpl->unset_version_number();
2734 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2738 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2740 path dir = "build/test/verify_missing_extension_metadata1";
2741 auto dcp = make_simple (dir);
2744 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2745 auto cpl = dcp->cpls()[0];
2748 Editor e (cpl->file().get());
2749 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2752 check_verify_result (
2755 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2756 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2761 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2763 path dir = "build/test/verify_missing_extension_metadata2";
2764 auto dcp = make_simple (dir);
2767 auto cpl = dcp->cpls()[0];
2770 Editor e (cpl->file().get());
2771 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2774 check_verify_result (
2777 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2778 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2783 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2785 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2786 auto dcp = make_simple (dir);
2789 auto const cpl = dcp->cpls()[0];
2792 Editor e (cpl->file().get());
2793 e.replace ("<meta:Name>A", "<meta:NameX>A");
2794 e.replace ("n</meta:Name>", "n</meta:NameX>");
2797 check_verify_result (
2800 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2801 { 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 },
2802 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2807 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2809 path dir = "build/test/verify_invalid_extension_metadata1";
2810 auto dcp = make_simple (dir);
2813 auto cpl = dcp->cpls()[0];
2816 Editor e (cpl->file().get());
2817 e.replace ("Application", "Fred");
2820 check_verify_result (
2823 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2824 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2829 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2831 path dir = "build/test/verify_invalid_extension_metadata2";
2832 auto dcp = make_simple (dir);
2835 auto cpl = dcp->cpls()[0];
2838 Editor e (cpl->file().get());
2839 e.replace ("DCP Constraints Profile", "Fred");
2842 check_verify_result (
2845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2846 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2851 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2853 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2854 auto dcp = make_simple (dir);
2857 auto const cpl = dcp->cpls()[0];
2860 Editor e (cpl->file().get());
2861 e.replace ("<meta:Value>", "<meta:ValueX>");
2862 e.replace ("</meta:Value>", "</meta:ValueX>");
2865 check_verify_result (
2868 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2869 { 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 },
2870 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2875 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2877 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2878 auto dcp = make_simple (dir);
2881 auto const cpl = dcp->cpls()[0];
2884 Editor e (cpl->file().get());
2885 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2888 check_verify_result (
2891 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2892 { 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() },
2897 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2899 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2900 auto dcp = make_simple (dir);
2903 auto const cpl = dcp->cpls()[0];
2906 Editor e (cpl->file().get());
2907 e.replace ("<meta:Property>", "<meta:PropertyX>");
2908 e.replace ("</meta:Property>", "</meta:PropertyX>");
2911 check_verify_result (
2914 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2915 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2916 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2921 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2923 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2924 auto dcp = make_simple (dir);
2927 auto const cpl = dcp->cpls()[0];
2930 Editor e (cpl->file().get());
2931 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2932 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2935 check_verify_result (
2938 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2939 { 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 },
2940 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2946 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2948 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2949 prepare_directory (dir);
2950 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2951 copy_file (i.path(), dir / i.path().filename());
2954 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2955 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2959 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2962 check_verify_result (
2965 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2967 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2969 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2970 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2971 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2972 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2977 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2979 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2980 prepare_directory (dir);
2981 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2982 copy_file (i.path(), dir / i.path().filename());
2985 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2986 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2989 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2992 check_verify_result (
2995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2996 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2997 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2998 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2999 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3000 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
3001 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
3006 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3008 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3009 prepare_directory (dir);
3010 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3011 copy_file (i.path(), dir / i.path().filename());
3015 Editor e (dir / dcp_test1_pkl);
3016 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3019 check_verify_result ({dir}, {});
3023 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3025 path dir ("build/test/verify_must_not_be_partially_encrypted");
3026 prepare_directory (dir);
3030 auto signer = make_shared<dcp::CertificateChain>();
3031 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3032 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3033 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3034 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3036 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3040 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3043 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3044 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3045 for (int i = 0; i < 24; ++i) {
3046 writer->write (j2c.data(), j2c.size());
3048 writer->finalize ();
3050 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3052 auto reel = make_shared<dcp::Reel>(
3053 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3054 make_shared<dcp::ReelSoundAsset>(ms, 0)
3057 reel->add (simple_markers());
3061 cpl->set_content_version (
3062 {"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"}
3064 cpl->set_annotation_text ("A Test DCP");
3065 cpl->set_issuer ("OpenDCP 0.0.25");
3066 cpl->set_creator ("OpenDCP 0.0.25");
3067 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3068 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3069 cpl->set_main_sound_sample_rate (48000);
3070 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3071 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3072 cpl->set_version_number (1);
3076 d.set_issuer("OpenDCP 0.0.25");
3077 d.set_creator("OpenDCP 0.0.25");
3078 d.set_issue_date("2012-07-17T04:45:18+00:00");
3079 d.set_annotation_text("A Test DCP");
3080 d.write_xml(signer);
3082 check_verify_result (
3085 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3090 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3092 vector<dcp::VerificationNote> notes;
3093 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"));
3094 auto reader = picture.start_read ();
3095 auto frame = reader->get_frame (0);
3096 verify_j2k(frame, 0, 24, notes);
3097 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3101 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3103 vector<dcp::VerificationNote> notes;
3104 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3105 auto reader = picture.start_read ();
3106 auto frame = reader->get_frame (0);
3107 verify_j2k(frame, 0, 24, notes);
3108 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3112 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3114 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3115 prepare_directory (dir);
3116 auto dcp = make_simple (dir);
3118 vector<dcp::VerificationNote> notes;
3119 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3120 auto reader = picture.start_read ();
3121 auto frame = reader->get_frame (0);
3122 verify_j2k(frame, 0, 24, notes);
3123 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3127 /** Check that ResourceID and the XML ID being different is spotted */
3128 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3130 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3131 prepare_directory (dir);
3133 ASDCP::WriterInfo writer_info;
3134 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3137 auto mxf_id = dcp::make_uuid ();
3138 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3139 BOOST_REQUIRE (c == Kumu::UUID_Length);
3141 auto resource_id = dcp::make_uuid ();
3142 ASDCP::TimedText::TimedTextDescriptor descriptor;
3143 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3144 DCP_ASSERT (c == Kumu::UUID_Length);
3146 auto xml_id = dcp::make_uuid ();
3147 ASDCP::TimedText::MXFWriter writer;
3148 auto subs_mxf = dir / "subs.mxf";
3149 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3150 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3151 writer.WriteTimedTextResource (dcp::String::compose(
3152 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3153 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3154 "<Id>urn:uuid:%1</Id>"
3155 "<ContentTitleText>Content</ContentTitleText>"
3156 "<AnnotationText>Annotation</AnnotationText>"
3157 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3158 "<ReelNumber>1</ReelNumber>"
3159 "<Language>en-US</Language>"
3160 "<EditRate>25 1</EditRate>"
3161 "<TimeCodeRate>25</TimeCodeRate>"
3162 "<StartTime>00:00:00:00</StartTime>"
3163 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3165 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3166 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3167 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3176 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3177 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3179 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3181 check_verify_result (
3184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3185 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3186 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3187 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3192 /** Check that ResourceID and the MXF ID being the same is spotted */
3193 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3195 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3196 prepare_directory (dir);
3198 ASDCP::WriterInfo writer_info;
3199 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3202 auto mxf_id = dcp::make_uuid ();
3203 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3204 BOOST_REQUIRE (c == Kumu::UUID_Length);
3206 auto resource_id = mxf_id;
3207 ASDCP::TimedText::TimedTextDescriptor descriptor;
3208 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3209 DCP_ASSERT (c == Kumu::UUID_Length);
3211 auto xml_id = resource_id;
3212 ASDCP::TimedText::MXFWriter writer;
3213 auto subs_mxf = dir / "subs.mxf";
3214 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3215 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3216 writer.WriteTimedTextResource (dcp::String::compose(
3217 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3218 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3219 "<Id>urn:uuid:%1</Id>"
3220 "<ContentTitleText>Content</ContentTitleText>"
3221 "<AnnotationText>Annotation</AnnotationText>"
3222 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3223 "<ReelNumber>1</ReelNumber>"
3224 "<Language>en-US</Language>"
3225 "<EditRate>25 1</EditRate>"
3226 "<TimeCodeRate>25</TimeCodeRate>"
3227 "<StartTime>00:00:00:00</StartTime>"
3228 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3230 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3231 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3232 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3241 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3242 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3244 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3246 check_verify_result (
3249 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3250 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3251 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3252 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3253 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3258 /** Check a DCP with a 3D asset marked as 2D */
3259 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3261 check_verify_result (
3262 { private_test / "data" / "xm" },
3265 dcp::VerificationNote::Type::WARNING,
3266 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3269 dcp::VerificationNote::Type::BV21_ERROR,
3270 dcp::VerificationNote::Code::INVALID_STANDARD
3277 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3279 path dir = "build/test/verify_unexpected_things_in_main_markers";
3280 prepare_directory (dir);
3281 auto dcp = make_simple (dir, 1, 24);
3285 Editor e (find_cpl(dir));
3287 " <IntrinsicDuration>24</IntrinsicDuration>",
3288 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3292 dcp::CPL cpl (find_cpl(dir));
3294 check_verify_result (
3297 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3298 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3299 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3304 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3306 path dir = "build/test/verify_invalid_content_kind";
3307 prepare_directory (dir);
3308 auto dcp = make_simple (dir, 1, 24);
3312 Editor e(find_cpl(dir));
3313 e.replace("trailer", "trip");
3316 dcp::CPL cpl (find_cpl(dir));
3318 check_verify_result (
3321 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3322 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3328 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3330 path dir = "build/test/verify_valid_content_kind";
3331 prepare_directory (dir);
3332 auto dcp = make_simple (dir, 1, 24);
3336 Editor e(find_cpl(dir));
3337 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3340 dcp::CPL cpl (find_cpl(dir));
3342 check_verify_result (
3345 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3351 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3353 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3354 prepare_directory(dir);
3355 auto dcp = make_simple(dir, 1, 24);
3358 auto constexpr area = "<meta:MainPictureActiveArea>";
3361 Editor e(find_cpl(dir));
3362 e.delete_lines_after(area, 2);
3363 e.insert(area, "<meta:Height>4080</meta:Height>");
3364 e.insert(area, "<meta:Width>1997</meta:Width>");
3367 dcp::PKL pkl(find_pkl(dir));
3368 dcp::CPL cpl(find_cpl(dir));
3370 check_verify_result(
3373 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(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::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3404 { 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)) },
3405 { 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)) },
3410 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3414 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3415 prepare_directory(dir);
3416 auto dcp = make_simple(dir, 1, 24);
3420 Editor e(find_pkl(dir));
3421 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3424 dcp::PKL pkl(find_pkl(dir));
3426 check_verify_result(
3429 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3434 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3438 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3439 prepare_directory(dir);
3440 auto dcp = make_simple(dir, 1, 24);
3444 Editor e(find_asset_map(dir));
3445 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3448 dcp::PKL pkl(find_pkl(dir));
3449 dcp::AssetMap asset_map(find_asset_map(dir));
3451 check_verify_result(
3454 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3455 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3460 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3462 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3464 dcp::MXFMetadata mxf_meta;
3465 mxf_meta.company_name = "OpenDCP";
3466 mxf_meta.product_name = "OpenDCP";
3467 mxf_meta.product_version = "0.0.25";
3469 auto constexpr sample_rate = 48000;
3470 auto constexpr frames = 240;
3472 boost::filesystem::remove_all(path);
3473 boost::filesystem::create_directories(path);
3474 auto dcp = make_shared<dcp::DCP>(path);
3475 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3476 cpl->set_annotation_text("hello");
3477 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3478 cpl->set_main_sound_sample_rate(sample_rate);
3479 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3480 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3481 cpl->set_version_number(1);
3485 /* Reel with 2 channels of audio */
3487 auto mp = simple_picture(path, "1", frames, {});
3488 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3490 auto reel = make_shared<dcp::Reel>(
3491 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3492 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3495 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3496 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3503 /* Reel with 6 channels of audio */
3505 auto mp = simple_picture(path, "2", frames, {});
3506 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3508 auto reel = make_shared<dcp::Reel>(
3509 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3510 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3513 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3514 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3521 dcp->set_annotation_text("hello");
3524 check_verify_result(
3527 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3532 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3534 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3536 dcp::MXFMetadata mxf_meta;
3537 mxf_meta.company_name = "OpenDCP";
3538 mxf_meta.product_name = "OpenDCP";
3539 mxf_meta.product_version = "0.0.25";
3541 auto constexpr sample_rate = 48000;
3542 auto constexpr frames = 240;
3544 boost::filesystem::remove_all(path);
3545 boost::filesystem::create_directories(path);
3546 auto dcp = make_shared<dcp::DCP>(path);
3547 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3548 cpl->set_annotation_text("hello");
3549 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3550 cpl->set_main_sound_sample_rate(sample_rate);
3551 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3552 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3553 cpl->set_version_number(1);
3555 auto mp = simple_picture(path, "1", frames, {});
3556 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3558 auto reel = make_shared<dcp::Reel>(
3559 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3560 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3563 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3564 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3565 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3571 dcp->set_annotation_text("hello");
3574 check_verify_result(
3577 { 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)) },
3582 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3584 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3585 auto constexpr video_frames = 24;
3586 auto constexpr sample_rate = 48000;
3588 boost::filesystem::remove_all(path);
3589 boost::filesystem::create_directories(path);
3591 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3592 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3594 dcp::Size const size(1998, 1080);
3595 auto image = make_shared<dcp::OpenJPEGImage>(size);
3596 boost::random::mt19937 rng(1);
3597 boost::random::uniform_int_distribution<> dist(0, 4095);
3598 for (int c = 0; c < 3; ++c) {
3599 for (int p = 0; p < (1998 * 1080); ++p) {
3600 image->data(c)[p] = dist(rng);
3603 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3604 for (int i = 0; i < 24; ++i) {
3605 picture_writer->write(j2c.data(), j2c.size());
3607 picture_writer->finalize();
3609 auto dcp = make_shared<dcp::DCP>(path);
3610 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3611 cpl->set_content_version(
3612 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3614 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3615 cpl->set_main_sound_sample_rate(sample_rate);
3616 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3617 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3618 cpl->set_version_number(1);
3620 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3622 auto reel = make_shared<dcp::Reel>(
3623 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3624 make_shared<dcp::ReelSoundAsset>(ms, 0)
3629 dcp->set_annotation_text("A Test DCP");
3632 check_verify_result(
3635 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3636 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3638 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3643 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3645 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3646 check_verify_result(
3649 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3650 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3651 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3652 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3653 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3654 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3659 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3661 path const dir("build/test/verify_missing_load_font");
3662 prepare_directory (dir);
3663 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3665 Editor editor(dir / "subs.xml");
3666 editor.delete_first_line_containing("LoadFont");
3668 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3669 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3670 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3672 check_verify_result (
3674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3675 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3681 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3683 boost::filesystem::path const dir = dcp::String::compose("build/test/%1", boost::unit_test::framework::current_test_case().full_name());
3684 prepare_directory(dir);
3685 auto dcp = make_simple (dir, 1, 202);
3688 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3689 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3690 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3691 "<ContentTitleText>Content</ContentTitleText>"
3692 "<AnnotationText>Annotation</AnnotationText>"
3693 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3694 "<ReelNumber>1</ReelNumber>"
3695 "<EditRate>24 1</EditRate>"
3696 "<TimeCodeRate>24</TimeCodeRate>"
3697 "<StartTime>00:00:00:00</StartTime>"
3698 "<Language>de-DE</Language>"
3700 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3701 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3702 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3708 dcp::File xml_file(dir / "subs.xml", "w");
3709 BOOST_REQUIRE(xml_file);
3710 xml_file.write(xml.c_str(), xml.size(), 1);
3712 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3713 subs->write(dir / "subs.mxf");
3715 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3716 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3719 check_verify_result (
3722 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())