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);
1529 dcp->set_annotation_text("A Test DCP");
1532 check_verify_result (
1535 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1536 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1541 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1543 path path ("build/test/verify_mismatched_subtitle_languages");
1544 auto constexpr reel_length = 192;
1545 auto dcp = make_simple (path, 2, reel_length);
1546 auto cpl = dcp->cpls()[0];
1549 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1550 subs->set_language (dcp::LanguageTag("de-DE"));
1551 subs->add (simple_subtitle());
1553 subs->write (path / "subs1.mxf");
1554 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1555 cpl->reels()[0]->add(reel_subs);
1559 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1560 subs->set_language (dcp::LanguageTag("en-US"));
1561 subs->add (simple_subtitle());
1563 subs->write (path / "subs2.mxf");
1564 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1565 cpl->reels()[1]->add(reel_subs);
1568 dcp->set_annotation_text("A Test DCP");
1571 check_verify_result (
1574 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1575 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1576 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1581 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1583 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1584 auto constexpr reel_length = 192;
1585 auto dcp = make_simple (path, 2, reel_length);
1586 auto cpl = dcp->cpls()[0];
1589 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1590 ccaps->set_language (dcp::LanguageTag("de-DE"));
1591 ccaps->add (simple_subtitle());
1593 ccaps->write (path / "subs1.mxf");
1594 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1595 cpl->reels()[0]->add(reel_ccaps);
1599 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1600 ccaps->set_language (dcp::LanguageTag("en-US"));
1601 ccaps->add (simple_subtitle());
1603 ccaps->write (path / "subs2.mxf");
1604 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1605 cpl->reels()[1]->add(reel_ccaps);
1608 dcp->set_annotation_text("A Test DCP");
1611 check_verify_result (
1614 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1615 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1620 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1622 path dir = "build/test/verify_missing_subtitle_start_time";
1623 prepare_directory (dir);
1624 auto dcp = make_simple (dir, 1, 106);
1627 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1628 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1629 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1630 "<ContentTitleText>Content</ContentTitleText>"
1631 "<AnnotationText>Annotation</AnnotationText>"
1632 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1633 "<ReelNumber>1</ReelNumber>"
1634 "<Language>de-DE</Language>"
1635 "<EditRate>24 1</EditRate>"
1636 "<TimeCodeRate>24</TimeCodeRate>"
1637 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1639 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1640 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1641 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1647 dcp::File xml_file(dir / "subs.xml", "w");
1648 BOOST_REQUIRE (xml_file);
1649 xml_file.write(xml.c_str(), xml.size(), 1);
1651 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1652 subs->write (dir / "subs.mxf");
1654 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1655 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1656 dcp->set_annotation_text("A Test DCP");
1659 check_verify_result (
1662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1663 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1668 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1670 path dir = "build/test/verify_invalid_subtitle_start_time";
1671 prepare_directory (dir);
1672 auto dcp = make_simple (dir, 1, 106);
1675 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1676 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
1677 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1678 "<ContentTitleText>Content</ContentTitleText>"
1679 "<AnnotationText>Annotation</AnnotationText>"
1680 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1681 "<ReelNumber>1</ReelNumber>"
1682 "<Language>de-DE</Language>"
1683 "<EditRate>24 1</EditRate>"
1684 "<TimeCodeRate>24</TimeCodeRate>"
1685 "<StartTime>00:00:02:00</StartTime>"
1686 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1688 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1689 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1690 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1696 dcp::File xml_file(dir / "subs.xml", "w");
1697 BOOST_REQUIRE (xml_file);
1698 xml_file.write(xml.c_str(), xml.size(), 1);
1700 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1701 subs->write (dir / "subs.mxf");
1703 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1704 dcp->cpls().front()->reels().front()->add(reel_subs);
1705 dcp->set_annotation_text("A Test DCP");
1708 check_verify_result (
1711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1712 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1720 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1723 , v_position(v_position_)
1731 dcp::VAlign v_align;
1737 shared_ptr<dcp::CPL>
1738 dcp_with_text (path dir, vector<TestText> subs)
1740 prepare_directory (dir);
1741 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1742 asset->set_start_time (dcp::Time());
1743 for (auto i: subs) {
1744 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1746 asset->set_language (dcp::LanguageTag("de-DE"));
1748 asset->write (dir / "subs.mxf");
1750 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1751 return write_dcp_with_single_asset (dir, reel_asset);
1756 shared_ptr<dcp::CPL>
1757 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1759 prepare_directory (dir);
1760 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1761 asset->set_start_time (dcp::Time());
1762 asset->set_language (dcp::LanguageTag("de-DE"));
1764 auto subs_mxf = dir / "subs.mxf";
1765 asset->write (subs_mxf);
1767 /* The call to write() puts the asset into the DCP correctly but it will have
1768 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1771 ASDCP::TimedText::MXFWriter writer;
1772 ASDCP::WriterInfo writer_info;
1773 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1775 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1776 DCP_ASSERT (c == Kumu::UUID_Length);
1777 ASDCP::TimedText::TimedTextDescriptor descriptor;
1778 descriptor.ContainerDuration = asset->intrinsic_duration();
1779 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1780 DCP_ASSERT (c == Kumu::UUID_Length);
1781 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1782 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1783 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1784 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1787 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1788 return write_dcp_with_single_asset (dir, reel_asset);
1792 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1794 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1795 /* Just too early */
1796 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1797 check_verify_result (
1800 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1801 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1807 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1809 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1810 /* Just late enough */
1811 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1812 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1816 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1818 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1819 prepare_directory (dir);
1821 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1822 asset1->set_start_time (dcp::Time());
1823 /* Just late enough */
1824 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1825 asset1->set_language (dcp::LanguageTag("de-DE"));
1827 asset1->write (dir / "subs1.mxf");
1828 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1829 auto reel1 = make_shared<dcp::Reel>();
1830 reel1->add (reel_asset1);
1831 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1832 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1833 reel1->add (markers1);
1835 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1836 asset2->set_start_time (dcp::Time());
1838 /* This would be too early on first reel but should be OK on the second */
1839 add_test_subtitle (asset2, 3, 4 * 24);
1840 asset2->set_language (dcp::LanguageTag("de-DE"));
1841 asset2->write (dir / "subs2.mxf");
1842 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1843 auto reel2 = make_shared<dcp::Reel>();
1844 reel2->add (reel_asset2);
1845 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1846 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1847 reel2->add (markers2);
1849 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1852 auto dcp = make_shared<dcp::DCP>(dir);
1854 dcp->set_annotation_text("hello");
1857 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1861 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1863 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1864 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1868 { 5 * 24 + 1, 6 * 24 },
1870 check_verify_result (
1873 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1874 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1879 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1881 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1882 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1886 { 5 * 24 + 16, 8 * 24 },
1888 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1892 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1894 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1895 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1896 check_verify_result (
1899 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1905 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1907 auto const dir = path("build/test/verify_valid_subtitle_duration");
1908 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1909 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1913 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1915 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1916 prepare_directory (dir);
1917 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1918 asset->set_start_time (dcp::Time());
1919 add_test_subtitle (asset, 0, 4 * 24);
1921 asset->set_language (dcp::LanguageTag("de-DE"));
1922 asset->write (dir / "subs.mxf");
1924 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1925 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1926 check_verify_result (
1929 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1931 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1932 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1938 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1940 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1941 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1944 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1945 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1946 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1947 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1949 check_verify_result (
1952 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1953 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1958 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1960 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1961 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1964 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1965 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1966 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1968 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1972 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1974 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1975 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1978 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1979 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1980 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1981 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1983 check_verify_result (
1986 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1992 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1994 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1995 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1998 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1999 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2000 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2001 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2003 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2007 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
2009 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
2010 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2013 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
2015 check_verify_result (
2018 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
2019 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2024 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
2026 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
2027 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
2030 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
2032 check_verify_result (
2035 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
2036 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2041 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
2043 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
2044 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2047 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2048 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2049 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2050 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
2052 check_verify_result (
2055 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2056 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2061 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
2063 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
2064 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2067 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
2068 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
2069 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
2071 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2075 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
2077 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
2078 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2081 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2082 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2083 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2084 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
2086 check_verify_result (
2089 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
2090 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2095 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
2097 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
2098 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2101 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
2102 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
2103 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
2104 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2106 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2110 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2112 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2113 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2116 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2118 check_verify_result (
2121 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2126 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2128 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2129 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2132 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2134 check_verify_result (
2137 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2143 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2145 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2146 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2149 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2150 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2151 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2153 check_verify_result (
2156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2161 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2163 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2164 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2167 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2168 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2169 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2171 check_verify_result (
2174 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2175 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2180 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2182 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2183 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2186 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2187 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2188 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2190 check_verify_result (
2193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2198 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2200 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2201 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2204 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2205 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2206 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2208 check_verify_result (
2211 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2216 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2218 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2219 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2220 check_verify_result (
2223 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2224 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2229 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2231 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2232 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2233 check_verify_result (
2236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2242 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2244 path const dir("build/test/verify_invalid_sound_frame_rate");
2245 prepare_directory (dir);
2247 auto picture = simple_picture (dir, "foo");
2248 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2249 auto reel = make_shared<dcp::Reel>();
2250 reel->add (reel_picture);
2251 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2252 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2253 reel->add (reel_sound);
2254 reel->add (simple_markers());
2255 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2257 auto dcp = make_shared<dcp::DCP>(dir);
2259 dcp->set_annotation_text("hello");
2262 check_verify_result (
2265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2266 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2271 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2273 path const dir("build/test/verify_missing_cpl_annotation_text");
2274 auto dcp = make_simple (dir);
2275 dcp->set_annotation_text("A Test DCP");
2278 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2280 auto const cpl = dcp->cpls()[0];
2283 BOOST_REQUIRE (cpl->file());
2284 Editor e(cpl->file().get());
2285 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2288 check_verify_result (
2291 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2292 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2297 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2299 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2300 auto dcp = make_simple (dir);
2301 dcp->set_annotation_text("A Test DCP");
2304 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2305 auto const cpl = dcp->cpls()[0];
2308 BOOST_REQUIRE (cpl->file());
2309 Editor e(cpl->file().get());
2310 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2313 check_verify_result (
2316 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2317 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2322 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2324 path const dir("build/test/verify_mismatched_asset_duration");
2325 prepare_directory (dir);
2326 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2327 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2329 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2330 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2332 auto reel = make_shared<dcp::Reel>(
2333 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2334 make_shared<dcp::ReelSoundAsset>(ms, 0)
2337 reel->add (simple_markers());
2341 dcp->set_annotation_text("A Test DCP");
2344 check_verify_result (
2347 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2348 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2355 shared_ptr<dcp::CPL>
2356 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2358 prepare_directory (dir);
2359 auto dcp = make_shared<dcp::DCP>(dir);
2360 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2362 auto constexpr reel_length = 192;
2364 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2365 subs->set_language (dcp::LanguageTag("de-DE"));
2366 subs->set_start_time (dcp::Time());
2367 subs->add (simple_subtitle());
2369 subs->write (dir / "subs.mxf");
2370 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2372 auto reel1 = make_shared<dcp::Reel>(
2373 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2374 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2378 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2381 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2382 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2383 reel1->add (markers1);
2387 auto reel2 = make_shared<dcp::Reel>(
2388 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2389 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2393 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2396 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2397 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2398 reel2->add (markers2);
2403 dcp->set_annotation_text("A Test DCP");
2410 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2413 path dir ("build/test/missing_main_subtitle_from_some_reels");
2414 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2415 check_verify_result (
2418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2419 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2425 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2426 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2427 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2431 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2432 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2433 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2439 shared_ptr<dcp::CPL>
2440 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2442 prepare_directory (dir);
2443 auto dcp = make_shared<dcp::DCP>(dir);
2444 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2446 auto constexpr reel_length = 192;
2448 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2449 subs->set_language (dcp::LanguageTag("de-DE"));
2450 subs->set_start_time (dcp::Time());
2451 subs->add (simple_subtitle());
2453 subs->write (dir / "subs.mxf");
2455 auto reel1 = make_shared<dcp::Reel>(
2456 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2457 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2460 for (int i = 0; i < caps_in_reel1; ++i) {
2461 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2464 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2465 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2466 reel1->add (markers1);
2470 auto reel2 = make_shared<dcp::Reel>(
2471 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2472 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2475 for (int i = 0; i < caps_in_reel2; ++i) {
2476 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2479 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2480 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2481 reel2->add (markers2);
2486 dcp->set_annotation_text("A Test DCP");
2493 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2496 path dir ("build/test/mismatched_closed_caption_asset_counts");
2497 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2498 check_verify_result (
2501 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2502 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2507 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2508 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2509 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2513 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2514 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2515 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2522 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2524 prepare_directory (dir);
2525 auto dcp = make_shared<dcp::DCP>(dir);
2526 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2528 auto constexpr reel_length = 192;
2530 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2531 subs->set_language (dcp::LanguageTag("de-DE"));
2532 subs->set_start_time (dcp::Time());
2533 subs->add (simple_subtitle());
2535 subs->write (dir / "subs.mxf");
2536 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2539 auto reel = make_shared<dcp::Reel>(
2540 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2541 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2544 reel->add (reel_text);
2546 reel->add (simple_markers(reel_length));
2551 dcp->set_annotation_text("A Test DCP");
2554 check_verify_result (
2557 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2558 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2563 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2565 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2566 "build/test/verify_subtitle_entry_point_must_be_present",
2567 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2568 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2569 asset->unset_entry_point ();
2573 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2574 "build/test/verify_subtitle_entry_point_must_be_zero",
2575 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2576 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2577 asset->set_entry_point (4);
2581 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2582 "build/test/verify_closed_caption_entry_point_must_be_present",
2583 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2584 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2585 asset->unset_entry_point ();
2589 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2590 "build/test/verify_closed_caption_entry_point_must_be_zero",
2591 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2592 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2593 asset->set_entry_point (9);
2599 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2603 path const dir("build/test/verify_missing_hash");
2604 auto dcp = make_simple (dir);
2605 dcp->set_annotation_text("A Test DCP");
2608 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2609 auto const cpl = dcp->cpls()[0];
2610 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2611 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2612 auto asset_id = cpl->reels()[0]->main_picture()->id();
2615 BOOST_REQUIRE (cpl->file());
2616 Editor e(cpl->file().get());
2617 e.delete_first_line_containing("<Hash>");
2620 check_verify_result (
2623 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2624 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2631 verify_markers_test (
2633 vector<pair<dcp::Marker, dcp::Time>> markers,
2634 vector<dcp::VerificationNote> test_notes
2637 auto dcp = make_simple (dir);
2638 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2639 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2640 for (auto const& i: markers) {
2641 markers_asset->set (i.first, i.second);
2643 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2644 dcp->set_annotation_text("A Test DCP");
2647 check_verify_result ({dir}, test_notes);
2651 BOOST_AUTO_TEST_CASE (verify_markers)
2653 verify_markers_test (
2654 "build/test/verify_markers_all_correct",
2656 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2657 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2658 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2659 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2664 verify_markers_test (
2665 "build/test/verify_markers_missing_ffec",
2667 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2668 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2669 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2675 verify_markers_test (
2676 "build/test/verify_markers_missing_ffmc",
2678 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2679 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2680 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2683 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2686 verify_markers_test (
2687 "build/test/verify_markers_missing_ffoc",
2689 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2690 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2691 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2697 verify_markers_test (
2698 "build/test/verify_markers_missing_lfoc",
2700 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2701 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2702 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2705 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2708 verify_markers_test (
2709 "build/test/verify_markers_incorrect_ffoc",
2711 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2712 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2713 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2714 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2717 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2720 verify_markers_test (
2721 "build/test/verify_markers_incorrect_lfoc",
2723 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2724 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2725 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2726 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2729 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2734 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2736 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2737 prepare_directory (dir);
2738 auto dcp = make_simple (dir);
2739 auto cpl = dcp->cpls()[0];
2740 cpl->unset_version_number();
2741 dcp->set_annotation_text("A Test DCP");
2744 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2748 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2750 path dir = "build/test/verify_missing_extension_metadata1";
2751 auto dcp = make_simple (dir);
2752 dcp->set_annotation_text("A Test DCP");
2755 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2756 auto cpl = dcp->cpls()[0];
2759 Editor e (cpl->file().get());
2760 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2763 check_verify_result (
2766 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2767 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2772 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2774 path dir = "build/test/verify_missing_extension_metadata2";
2775 auto dcp = make_simple (dir);
2776 dcp->set_annotation_text("A Test DCP");
2779 auto cpl = dcp->cpls()[0];
2782 Editor e (cpl->file().get());
2783 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2786 check_verify_result (
2789 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2790 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2795 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2797 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2798 auto dcp = make_simple (dir);
2799 dcp->set_annotation_text("A Test DCP");
2802 auto const cpl = dcp->cpls()[0];
2805 Editor e (cpl->file().get());
2806 e.replace ("<meta:Name>A", "<meta:NameX>A");
2807 e.replace ("n</meta:Name>", "n</meta:NameX>");
2810 check_verify_result (
2813 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2814 { 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 },
2815 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2820 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2822 path dir = "build/test/verify_invalid_extension_metadata1";
2823 auto dcp = make_simple (dir);
2824 dcp->set_annotation_text("A Test DCP");
2827 auto cpl = dcp->cpls()[0];
2830 Editor e (cpl->file().get());
2831 e.replace ("Application", "Fred");
2834 check_verify_result (
2837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2838 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2843 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2845 path dir = "build/test/verify_invalid_extension_metadata2";
2846 auto dcp = make_simple (dir);
2847 dcp->set_annotation_text("A Test DCP");
2850 auto cpl = dcp->cpls()[0];
2853 Editor e (cpl->file().get());
2854 e.replace ("DCP Constraints Profile", "Fred");
2857 check_verify_result (
2860 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2861 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2866 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2868 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2869 auto dcp = make_simple (dir);
2870 dcp->set_annotation_text("A Test DCP");
2873 auto const cpl = dcp->cpls()[0];
2876 Editor e (cpl->file().get());
2877 e.replace ("<meta:Value>", "<meta:ValueX>");
2878 e.replace ("</meta:Value>", "</meta:ValueX>");
2881 check_verify_result (
2884 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2885 { 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 },
2886 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2891 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2893 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2894 auto dcp = make_simple (dir);
2895 dcp->set_annotation_text("A Test DCP");
2898 auto const cpl = dcp->cpls()[0];
2901 Editor e (cpl->file().get());
2902 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2905 check_verify_result (
2908 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2909 { 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() },
2914 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2916 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2917 auto dcp = make_simple (dir);
2918 dcp->set_annotation_text("A Test DCP");
2921 auto const cpl = dcp->cpls()[0];
2924 Editor e (cpl->file().get());
2925 e.replace ("<meta:Property>", "<meta:PropertyX>");
2926 e.replace ("</meta:Property>", "</meta:PropertyX>");
2929 check_verify_result (
2932 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2933 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2934 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2939 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2941 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2942 auto dcp = make_simple (dir);
2943 dcp->set_annotation_text("A Test DCP");
2946 auto const cpl = dcp->cpls()[0];
2949 Editor e (cpl->file().get());
2950 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2951 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2954 check_verify_result (
2957 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2958 { 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 },
2959 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2965 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2967 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2968 prepare_directory (dir);
2969 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2970 copy_file (i.path(), dir / i.path().filename());
2973 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2974 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2978 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2981 check_verify_result (
2984 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2988 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2989 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2996 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2998 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2999 prepare_directory (dir);
3000 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
3001 copy_file (i.path(), dir / i.path().filename());
3004 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
3005 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
3008 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3011 check_verify_result (
3014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
3015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3016 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3017 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3018 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3019 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
3020 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
3025 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
3027 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
3028 prepare_directory (dir);
3029 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
3030 copy_file (i.path(), dir / i.path().filename());
3034 Editor e (dir / dcp_test1_pkl);
3035 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3038 check_verify_result ({dir}, {});
3042 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3044 path dir ("build/test/verify_must_not_be_partially_encrypted");
3045 prepare_directory (dir);
3049 auto signer = make_shared<dcp::CertificateChain>();
3050 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3051 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3052 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3053 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3055 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3059 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3062 auto writer = mp->start_write(dir / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3063 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3064 for (int i = 0; i < 24; ++i) {
3065 writer->write (j2c.data(), j2c.size());
3067 writer->finalize ();
3069 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3071 auto reel = make_shared<dcp::Reel>(
3072 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3073 make_shared<dcp::ReelSoundAsset>(ms, 0)
3076 reel->add (simple_markers());
3080 cpl->set_content_version (
3081 {"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"}
3083 cpl->set_annotation_text ("A Test DCP");
3084 cpl->set_issuer ("OpenDCP 0.0.25");
3085 cpl->set_creator ("OpenDCP 0.0.25");
3086 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3087 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
3088 cpl->set_main_sound_sample_rate (48000);
3089 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3090 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3091 cpl->set_version_number (1);
3095 d.set_issuer("OpenDCP 0.0.25");
3096 d.set_creator("OpenDCP 0.0.25");
3097 d.set_issue_date("2012-07-17T04:45:18+00:00");
3098 d.set_annotation_text("A Test DCP");
3099 d.write_xml(signer);
3101 check_verify_result (
3104 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3109 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3111 vector<dcp::VerificationNote> notes;
3112 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"));
3113 auto reader = picture.start_read ();
3114 auto frame = reader->get_frame (0);
3115 verify_j2k(frame, 0, 24, notes);
3116 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3120 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3122 vector<dcp::VerificationNote> notes;
3123 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3124 auto reader = picture.start_read ();
3125 auto frame = reader->get_frame (0);
3126 verify_j2k(frame, 0, 24, notes);
3127 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3131 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3133 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3134 prepare_directory (dir);
3135 auto dcp = make_simple (dir);
3137 vector<dcp::VerificationNote> notes;
3138 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3139 auto reader = picture.start_read ();
3140 auto frame = reader->get_frame (0);
3141 verify_j2k(frame, 0, 24, notes);
3142 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3146 /** Check that ResourceID and the XML ID being different is spotted */
3147 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3149 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3150 prepare_directory (dir);
3152 ASDCP::WriterInfo writer_info;
3153 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3156 auto mxf_id = dcp::make_uuid ();
3157 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3158 BOOST_REQUIRE (c == Kumu::UUID_Length);
3160 auto resource_id = dcp::make_uuid ();
3161 ASDCP::TimedText::TimedTextDescriptor descriptor;
3162 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3163 DCP_ASSERT (c == Kumu::UUID_Length);
3165 auto xml_id = dcp::make_uuid ();
3166 ASDCP::TimedText::MXFWriter writer;
3167 auto subs_mxf = dir / "subs.mxf";
3168 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3169 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3170 writer.WriteTimedTextResource (dcp::String::compose(
3171 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3172 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3173 "<Id>urn:uuid:%1</Id>"
3174 "<ContentTitleText>Content</ContentTitleText>"
3175 "<AnnotationText>Annotation</AnnotationText>"
3176 "<IssueDate>2018-10-02T12:25:14</IssueDate>"
3177 "<ReelNumber>1</ReelNumber>"
3178 "<Language>en-US</Language>"
3179 "<EditRate>25 1</EditRate>"
3180 "<TimeCodeRate>25</TimeCodeRate>"
3181 "<StartTime>00:00:00:00</StartTime>"
3182 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
3184 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3185 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3186 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3195 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3196 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3198 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3200 check_verify_result (
3203 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3204 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3205 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3206 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3211 /** Check that ResourceID and the MXF ID being the same is spotted */
3212 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3214 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3215 prepare_directory (dir);
3217 ASDCP::WriterInfo writer_info;
3218 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3221 auto mxf_id = dcp::make_uuid ();
3222 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3223 BOOST_REQUIRE (c == Kumu::UUID_Length);
3225 auto resource_id = mxf_id;
3226 ASDCP::TimedText::TimedTextDescriptor descriptor;
3227 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3228 DCP_ASSERT (c == Kumu::UUID_Length);
3230 auto xml_id = resource_id;
3231 ASDCP::TimedText::MXFWriter writer;
3232 auto subs_mxf = dir / "subs.mxf";
3233 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3234 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3235 writer.WriteTimedTextResource (dcp::String::compose(
3236 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3237 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3238 "<Id>urn:uuid:%1</Id>"
3239 "<ContentTitleText>Content</ContentTitleText>"
3240 "<AnnotationText>Annotation</AnnotationText>"
3241 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3242 "<ReelNumber>1</ReelNumber>"
3243 "<Language>en-US</Language>"
3244 "<EditRate>25 1</EditRate>"
3245 "<TimeCodeRate>25</TimeCodeRate>"
3246 "<StartTime>00:00:00:00</StartTime>"
3247 "<LoadFont ID=\"font\">urn:uuid:0ce6e0ba-58b9-4344-8929-4d9c959c2d55</LoadFont>"
3249 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3250 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3251 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3260 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3261 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3263 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3265 check_verify_result (
3268 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3269 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3270 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3271 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
3272 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_ISSUE_DATE, string{"2018-10-02T12:25:14+02:00"} }
3277 /** Check a DCP with a 3D asset marked as 2D */
3278 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3280 check_verify_result (
3281 { private_test / "data" / "xm" },
3284 dcp::VerificationNote::Type::WARNING,
3285 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3288 dcp::VerificationNote::Type::BV21_ERROR,
3289 dcp::VerificationNote::Code::INVALID_STANDARD
3296 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3298 path dir = "build/test/verify_unexpected_things_in_main_markers";
3299 prepare_directory (dir);
3300 auto dcp = make_simple (dir, 1, 24);
3301 dcp->set_annotation_text("A Test DCP");
3305 Editor e (find_cpl(dir));
3307 " <IntrinsicDuration>24</IntrinsicDuration>",
3308 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3312 dcp::CPL cpl (find_cpl(dir));
3314 check_verify_result (
3317 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3318 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3324 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3326 path dir = "build/test/verify_invalid_content_kind";
3327 prepare_directory (dir);
3328 auto dcp = make_simple (dir, 1, 24);
3329 dcp->set_annotation_text("A Test DCP");
3333 Editor e(find_cpl(dir));
3334 e.replace("trailer", "trip");
3337 dcp::CPL cpl (find_cpl(dir));
3339 check_verify_result (
3342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3349 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3351 path dir = "build/test/verify_valid_content_kind";
3352 prepare_directory (dir);
3353 auto dcp = make_simple (dir, 1, 24);
3354 dcp->set_annotation_text("A Test DCP");
3358 Editor e(find_cpl(dir));
3359 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3362 dcp::CPL cpl (find_cpl(dir));
3364 check_verify_result (
3367 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3373 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_1)
3375 path dir = "build/test/verify_invalid_main_picture_active_area_1";
3376 prepare_directory(dir);
3377 auto dcp = make_simple(dir, 1, 24);
3380 auto constexpr area = "<meta:MainPictureActiveArea>";
3383 Editor e(find_cpl(dir));
3384 e.delete_lines_after(area, 2);
3385 e.insert(area, "<meta:Height>4080</meta:Height>");
3386 e.insert(area, "<meta:Width>1997</meta:Width>");
3389 dcp::PKL pkl(find_pkl(dir));
3390 dcp::CPL cpl(find_cpl(dir));
3392 check_verify_result(
3395 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3396 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3397 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "width 1997 is not a multiple of 2", canonical(find_cpl(dir)) },
3398 { 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)) },
3403 BOOST_AUTO_TEST_CASE(verify_invalid_main_picture_active_area_2)
3405 path dir = "build/test/verify_invalid_main_picture_active_area_2";
3406 prepare_directory(dir);
3407 auto dcp = make_simple(dir, 1, 24);
3410 auto constexpr area = "<meta:MainPictureActiveArea>";
3413 Editor e(find_cpl(dir));
3414 e.delete_lines_after(area, 2);
3415 e.insert(area, "<meta:Height>5125</meta:Height>");
3416 e.insert(area, "<meta:Width>9900</meta:Width>");
3419 dcp::PKL pkl(find_pkl(dir));
3420 dcp::CPL cpl(find_cpl(dir));
3422 check_verify_result(
3425 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3426 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3427 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_MAIN_PICTURE_ACTIVE_AREA, "height 5125 is not a multiple of 2", canonical(find_cpl(dir)) },
3428 { 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)) },
3429 { 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)) },
3434 BOOST_AUTO_TEST_CASE(verify_duplicate_pkl_asset_ids)
3438 path dir = "build/test/verify_duplicate_pkl_asset_ids";
3439 prepare_directory(dir);
3440 auto dcp = make_simple(dir, 1, 24);
3444 Editor e(find_pkl(dir));
3445 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab");
3448 dcp::PKL pkl(find_pkl(dir));
3450 check_verify_result(
3453 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_PKL, pkl.id(), canonical(find_pkl(dir)) },
3458 BOOST_AUTO_TEST_CASE(verify_duplicate_assetmap_asset_ids)
3462 path dir = "build/test/verify_duplicate_assetmap_asset_ids";
3463 prepare_directory(dir);
3464 auto dcp = make_simple(dir, 1, 24);
3468 Editor e(find_asset_map(dir));
3469 e.replace("urn:uuid:5407b210-4441-4e97-8b16-8bdc7c12da54", "urn:uuid:97f0f352-5b77-48ee-a558-9df37717f4fa");
3472 dcp::PKL pkl(find_pkl(dir));
3473 dcp::AssetMap asset_map(find_asset_map(dir));
3475 check_verify_result(
3478 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl.id(), canonical(find_pkl(dir)), },
3479 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::DUPLICATE_ASSET_ID_IN_ASSETMAP, asset_map.id(), canonical(find_asset_map(dir)) },
3480 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, string("5407b210-4441-4e97-8b16-8bdc7c12da54") },
3485 BOOST_AUTO_TEST_CASE(verify_mismatched_sound_channel_counts)
3487 boost::filesystem::path const path = "build/test/verify_mismatched_sound_channel_counts";
3489 dcp::MXFMetadata mxf_meta;
3490 mxf_meta.company_name = "OpenDCP";
3491 mxf_meta.product_name = "OpenDCP";
3492 mxf_meta.product_version = "0.0.25";
3494 auto constexpr sample_rate = 48000;
3495 auto constexpr frames = 240;
3497 boost::filesystem::remove_all(path);
3498 boost::filesystem::create_directories(path);
3499 auto dcp = make_shared<dcp::DCP>(path);
3500 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3501 cpl->set_annotation_text("hello");
3502 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R"));
3503 cpl->set_main_sound_sample_rate(sample_rate);
3504 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3505 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3506 cpl->set_version_number(1);
3510 /* Reel with 2 channels of audio */
3512 auto mp = simple_picture(path, "1", frames, {});
3513 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3515 auto reel = make_shared<dcp::Reel>(
3516 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3517 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3520 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3521 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3528 /* Reel with 6 channels of audio */
3530 auto mp = simple_picture(path, "2", frames, {});
3531 auto ms = simple_sound(path, "2", mxf_meta, "en-US", frames, sample_rate, {}, 6);
3533 auto reel = make_shared<dcp::Reel>(
3534 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3535 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3538 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3539 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
3546 dcp->set_annotation_text("hello");
3549 check_verify_result(
3552 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_CHANNEL_COUNTS, canonical(find_file(path, "audio2")) },
3557 BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
3559 boost::filesystem::path const path = "build/test/verify_invalid_main_sound_configuration";
3561 dcp::MXFMetadata mxf_meta;
3562 mxf_meta.company_name = "OpenDCP";
3563 mxf_meta.product_name = "OpenDCP";
3564 mxf_meta.product_version = "0.0.25";
3566 auto constexpr sample_rate = 48000;
3567 auto constexpr frames = 240;
3569 boost::filesystem::remove_all(path);
3570 boost::filesystem::create_directories(path);
3571 auto dcp = make_shared<dcp::DCP>(path);
3572 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3573 cpl->set_annotation_text("hello");
3574 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3575 cpl->set_main_sound_sample_rate(sample_rate);
3576 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3577 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3578 cpl->set_version_number(1);
3580 auto mp = simple_picture(path, "1", frames, {});
3581 auto ms = simple_sound(path, "1", mxf_meta, "en-US", frames, sample_rate, {}, 2);
3583 auto reel = make_shared<dcp::Reel>(
3584 std::make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3585 std::make_shared<dcp::ReelSoundAsset>(ms, 0)
3588 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
3589 markers->set(dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
3590 markers->set(dcp::Marker::LFOC, dcp::Time(0, 0, 9, 23, 24));
3596 dcp->set_annotation_text("hello");
3599 check_verify_result(
3602 { 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)) },
3607 BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
3609 boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
3610 auto constexpr video_frames = 24;
3611 auto constexpr sample_rate = 48000;
3613 boost::filesystem::remove_all(path);
3614 boost::filesystem::create_directories(path);
3616 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
3617 auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
3619 dcp::Size const size(1998, 1080);
3620 auto image = make_shared<dcp::OpenJPEGImage>(size);
3621 boost::random::mt19937 rng(1);
3622 boost::random::uniform_int_distribution<> dist(0, 4095);
3623 for (int c = 0; c < 3; ++c) {
3624 for (int p = 0; p < (1998 * 1080); ++p) {
3625 image->data(c)[p] = dist(rng);
3628 auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
3629 for (int i = 0; i < 24; ++i) {
3630 picture_writer->write(j2c.data(), j2c.size());
3632 picture_writer->finalize();
3634 auto dcp = make_shared<dcp::DCP>(path);
3635 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3636 cpl->set_content_version(
3637 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
3639 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
3640 cpl->set_main_sound_sample_rate(sample_rate);
3641 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
3642 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
3643 cpl->set_version_number(1);
3645 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
3647 auto reel = make_shared<dcp::Reel>(
3648 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3649 make_shared<dcp::ReelSoundAsset>(ms, 0)
3654 dcp->set_annotation_text("A Test DCP");
3657 check_verify_result(
3660 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
3661 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
3662 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
3663 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
3668 BOOST_AUTO_TEST_CASE(verify_too_many_subtitle_namespaces)
3670 boost::filesystem::path const dir = "test/ref/DCP/subtitle_namespace_test";
3671 check_verify_result(
3674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
3675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
3676 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3677 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(find_file(dir, "sub_")) },
3678 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, "fc815694-7977-4a27-a8b3-32b9d4075e4c", canonical(find_file(dir, "cpl_")) },
3679 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_SUBTITLE_NAMESPACE_COUNT, std::string{"315de731-1173-484c-9a35-bdacf5a9d99d"} }
3684 BOOST_AUTO_TEST_CASE(verify_missing_load_font_for_font)
3686 path const dir("build/test/verify_missing_load_font");
3687 prepare_directory (dir);
3688 copy_file ("test/data/subs1.xml", dir / "subs.xml");
3690 Editor editor(dir / "subs.xml");
3691 editor.delete_first_line_containing("LoadFont");
3693 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
3694 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
3695 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
3697 check_verify_result (
3699 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
3700 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT_FOR_FONT).set_id("theFontId")
3706 BOOST_AUTO_TEST_CASE(verify_missing_load_font)
3708 boost::filesystem::path const dir = dcp::String::compose("build/test/%1", boost::unit_test::framework::current_test_case().full_name());
3709 prepare_directory(dir);
3710 auto dcp = make_simple (dir, 1, 202);
3713 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3714 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\">"
3715 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
3716 "<ContentTitleText>Content</ContentTitleText>"
3717 "<AnnotationText>Annotation</AnnotationText>"
3718 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3719 "<ReelNumber>1</ReelNumber>"
3720 "<EditRate>24 1</EditRate>"
3721 "<TimeCodeRate>24</TimeCodeRate>"
3722 "<StartTime>00:00:00:00</StartTime>"
3723 "<Language>de-DE</Language>"
3725 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3726 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:06:00\" TimeOut=\"00:00:08:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3727 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3733 dcp::File xml_file(dir / "subs.xml", "w");
3734 BOOST_REQUIRE(xml_file);
3735 xml_file.write(xml.c_str(), xml.size(), 1);
3737 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
3738 subs->write(dir / "subs.mxf");
3740 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 202, 0);
3741 dcp->cpls()[0]->reels()[0]->add(reel_subs);
3742 dcp->set_annotation_text("A Test DCP");
3745 check_verify_result (
3748 dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_LOAD_FONT).set_id(reel_subs->id())