2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/test/unit_test.hpp>
62 #include <boost/algorithm/string.hpp>
69 using std::make_shared;
71 using std::shared_ptr;
74 using boost::optional;
75 using namespace boost::filesystem;
78 static list<pair<string, optional<path>>> stages;
80 static string filename_to_id(boost::filesystem::path path)
82 return path.string().substr(4, path.string().length() - 8);
85 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
86 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
88 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
89 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
91 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
93 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
94 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
96 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
97 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
100 stage (string s, optional<path> p)
102 stages.push_back (make_pair (s, p));
112 prepare_directory (path path)
114 using namespace boost::filesystem;
116 create_directories (path);
120 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
121 * to make a new sacrifical test DCP.
124 setup (int reference_number, string verify_test_suffix)
126 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
127 prepare_directory (dir);
128 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
129 copy_file (i.path(), dir / i.path().filename());
138 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
140 auto reel = make_shared<dcp::Reel>();
141 reel->add (reel_asset);
142 reel->add (simple_markers());
144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
146 auto dcp = make_shared<dcp::DCP>(dir);
148 dcp->set_annotation_text("hello");
155 /** Class that can alter a file by searching and replacing strings within it.
156 * On destruction modifies the file whose name was given to the constructor.
164 _content = dcp::file_to_string (_path);
169 auto f = fopen(_path.string().c_str(), "w");
171 fwrite (_content.c_str(), _content.length(), 1, f);
175 void replace (string a, string b)
177 auto old_content = _content;
178 boost::algorithm::replace_all (_content, a, b);
179 BOOST_REQUIRE (_content != old_content);
182 void delete_first_line_containing (string s)
184 vector<string> lines;
185 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
186 auto old_content = _content;
189 for (auto i: lines) {
190 if (i.find(s) == string::npos || done) {
191 _content += i + "\n";
196 BOOST_REQUIRE (_content != old_content);
199 void delete_lines (string from, string to)
201 vector<string> lines;
202 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
203 bool deleting = false;
204 auto old_content = _content;
206 for (auto i: lines) {
207 if (i.find(from) != string::npos) {
211 _content += i + "\n";
213 if (deleting && i.find(to) != string::npos) {
217 BOOST_REQUIRE (_content != old_content);
220 void insert (string after, string line)
222 vector<string> lines;
223 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
224 auto old_content = _content;
226 bool replaced = false;
227 for (auto i: lines) {
229 if (!replaced && i.find(after) != string::npos) {
234 BOOST_REQUIRE (_content != old_content);
239 std::string _content;
243 LIBDCP_DISABLE_WARNINGS
246 dump_notes (vector<dcp::VerificationNote> const & notes)
248 for (auto i: notes) {
249 std::cout << dcp::note_to_string(i) << "\n";
252 LIBDCP_ENABLE_WARNINGS
257 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
259 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
260 std::sort (notes.begin(), notes.end());
261 std::sort (test_notes.begin(), test_notes.end());
263 string message = "\nVerification notes from test:\n";
264 for (auto i: notes) {
265 message += " " + note_to_string(i) + "\n";
267 message += "Expected:\n";
268 for (auto i: test_notes) {
269 message += " " + note_to_string(i) + "\n";
272 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
276 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
277 * replacing from with to. Verify the resulting DCP and check that the results match the given
282 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
284 auto dir = setup (1, suffix);
287 Editor e (file(suffix));
288 e.replace (from, to);
291 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
293 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
294 auto i = notes.begin();
295 auto j = codes.begin();
296 while (i != notes.end()) {
297 BOOST_CHECK_EQUAL (i->code(), *j);
304 BOOST_AUTO_TEST_CASE (verify_no_error)
307 auto dir = setup (1, "no_error");
308 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
310 path const cpl_file = dir / dcp_test1_cpl;
311 path const pkl_file = dir / dcp_test1_pkl;
312 path const assetmap_file = dir / "ASSETMAP.xml";
314 auto st = stages.begin();
315 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
316 BOOST_REQUIRE (st->second);
317 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
319 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
320 BOOST_REQUIRE (st->second);
321 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
323 BOOST_CHECK_EQUAL (st->first, "Checking reel");
324 BOOST_REQUIRE (!st->second);
326 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
327 BOOST_REQUIRE (st->second);
328 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
330 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
331 BOOST_REQUIRE (st->second);
332 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
334 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
335 BOOST_REQUIRE (st->second);
336 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
338 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
339 BOOST_REQUIRE (st->second);
340 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
342 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
343 BOOST_REQUIRE (st->second);
344 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
346 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
347 BOOST_REQUIRE (st->second);
348 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
350 BOOST_REQUIRE (st == stages.end());
352 BOOST_CHECK_EQUAL (notes.size(), 0U);
356 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
358 using namespace boost::filesystem;
360 auto dir = setup (1, "incorrect_picture_sound_hash");
362 auto video_path = path(dir / "video.mxf");
363 auto mod = fopen(video_path.string().c_str(), "r+b");
365 fseek (mod, 4096, SEEK_SET);
367 fwrite (&x, sizeof(x), 1, mod);
370 auto audio_path = path(dir / "audio.mxf");
371 mod = fopen(audio_path.string().c_str(), "r+b");
373 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
374 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
377 dcp::ASDCPErrorSuspender sus;
378 check_verify_result (
381 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
382 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
387 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
389 using namespace boost::filesystem;
391 auto dir = setup (1, "mismatched_picture_sound_hashes");
394 Editor e (dir / dcp_test1_pkl);
395 e.replace ("<Hash>", "<Hash>x");
398 check_verify_result (
401 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
402 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
403 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
404 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
405 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
406 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
411 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
413 auto dir = setup (1, "failed_read_content_kind");
416 Editor e (dir / dcp_test1_cpl);
417 e.replace ("<ContentKind>", "<ContentKind>x");
420 check_verify_result (
423 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
424 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
433 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
441 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
447 asset_map (string suffix)
449 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
453 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
455 check_verify_result_after_replace (
456 "invalid_picture_frame_rate", &cpl,
457 "<FrameRate>24 1", "<FrameRate>99 1",
458 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
459 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
463 BOOST_AUTO_TEST_CASE (verify_missing_asset)
465 auto dir = setup (1, "missing_asset");
466 remove (dir / "video.mxf");
467 check_verify_result (
470 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
475 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
477 check_verify_result_after_replace (
478 "empty_asset_path", &asset_map,
479 "<Path>video.mxf</Path>", "<Path></Path>",
480 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
485 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
487 check_verify_result_after_replace (
488 "mismatched_standard", &cpl,
489 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
490 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
491 dcp::VerificationNote::Code::INVALID_XML,
492 dcp::VerificationNote::Code::INVALID_XML,
493 dcp::VerificationNote::Code::INVALID_XML,
494 dcp::VerificationNote::Code::INVALID_XML,
495 dcp::VerificationNote::Code::INVALID_XML,
496 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
501 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
503 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
504 check_verify_result_after_replace (
505 "invalid_xml_cpl_id", &cpl,
506 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
507 { dcp::VerificationNote::Code::INVALID_XML }
512 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
514 check_verify_result_after_replace (
515 "invalid_xml_issue_date", &cpl,
516 "<IssueDate>", "<IssueDate>x",
517 { dcp::VerificationNote::Code::INVALID_XML,
518 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
523 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
525 check_verify_result_after_replace (
526 "invalid_xml_pkl_id", &pkl,
527 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
528 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
529 { dcp::VerificationNote::Code::INVALID_XML }
534 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
536 check_verify_result_after_replace (
537 "invalid_xml_asset_map_id", &asset_map,
538 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
539 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
540 { dcp::VerificationNote::Code::INVALID_XML }
545 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
548 auto dir = setup (3, "verify_invalid_standard");
549 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
551 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
552 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
553 path const assetmap_file = dir / "ASSETMAP";
555 auto st = stages.begin();
556 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
557 BOOST_REQUIRE (st->second);
558 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
560 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
561 BOOST_REQUIRE (st->second);
562 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
564 BOOST_CHECK_EQUAL (st->first, "Checking reel");
565 BOOST_REQUIRE (!st->second);
567 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
568 BOOST_REQUIRE (st->second);
569 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
571 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
572 BOOST_REQUIRE (st->second);
573 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
575 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
576 BOOST_REQUIRE (st->second);
577 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
579 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
580 BOOST_REQUIRE (st->second);
581 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
583 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
584 BOOST_REQUIRE (st->second);
585 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
587 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
588 BOOST_REQUIRE (st->second);
589 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
591 BOOST_REQUIRE (st == stages.end());
593 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
594 auto i = notes.begin ();
595 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
596 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
598 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
599 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
602 /* DCP with a short asset */
603 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
605 auto dir = setup (8, "invalid_duration");
606 check_verify_result (
609 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
610 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
611 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
613 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
614 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
621 dcp_from_frame (dcp::ArrayData const& frame, path dir)
623 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
624 create_directories (dir);
625 auto writer = asset->start_write (dir / "pic.mxf", true);
626 for (int i = 0; i < 24; ++i) {
627 writer->write (frame.data(), frame.size());
631 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
632 return write_dcp_with_single_asset (dir, reel_asset);
636 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
638 int const too_big = 1302083 * 2;
640 /* Compress a black image */
641 auto image = black_image ();
642 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
643 BOOST_REQUIRE (frame.size() < too_big);
645 /* Place it in a bigger block with some zero padding at the end */
646 dcp::ArrayData oversized_frame(too_big);
647 memcpy (oversized_frame.data(), frame.data(), frame.size());
648 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
650 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
651 prepare_directory (dir);
652 auto cpl = dcp_from_frame (oversized_frame, dir);
654 check_verify_result (
657 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
658 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
659 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
664 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
666 int const nearly_too_big = 1302083 * 0.98;
668 /* Compress a black image */
669 auto image = black_image ();
670 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
671 BOOST_REQUIRE (frame.size() < nearly_too_big);
673 /* Place it in a bigger block with some zero padding at the end */
674 dcp::ArrayData oversized_frame(nearly_too_big);
675 memcpy (oversized_frame.data(), frame.data(), frame.size());
676 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
678 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
679 prepare_directory (dir);
680 auto cpl = dcp_from_frame (oversized_frame, dir);
682 check_verify_result (
685 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
686 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
687 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
692 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
694 /* Compress a black image */
695 auto image = black_image ();
696 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
697 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
699 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
700 prepare_directory (dir);
701 auto cpl = dcp_from_frame (frame, dir);
703 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
707 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
709 path const dir("build/test/verify_valid_interop_subtitles");
710 prepare_directory (dir);
711 copy_file ("test/data/subs1.xml", dir / "subs.xml");
712 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
713 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
714 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
716 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
720 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
722 using namespace boost::filesystem;
724 path const dir("build/test/verify_invalid_interop_subtitles");
725 prepare_directory (dir);
726 copy_file ("test/data/subs1.xml", dir / "subs.xml");
727 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
728 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
729 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
732 Editor e (dir / "subs.xml");
733 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
736 check_verify_result (
739 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
740 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
742 dcp::VerificationNote::Type::ERROR,
743 dcp::VerificationNote::Code::INVALID_XML,
744 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
752 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
754 path const dir("build/test/verify_valid_smpte_subtitles");
755 prepare_directory (dir);
756 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
757 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
758 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
759 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
761 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
765 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
767 using namespace boost::filesystem;
769 path const dir("build/test/verify_invalid_smpte_subtitles");
770 prepare_directory (dir);
771 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
772 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
773 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
774 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
775 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
777 check_verify_result (
780 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
782 dcp::VerificationNote::Type::ERROR,
783 dcp::VerificationNote::Code::INVALID_XML,
784 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
788 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
789 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
794 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
796 path const dir("build/test/verify_empty_text_node_in_subtitles");
797 prepare_directory (dir);
798 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
799 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
800 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
801 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
803 check_verify_result (
806 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
808 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
814 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
815 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
817 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
818 prepare_directory (dir);
819 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
820 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
821 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
822 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
824 check_verify_result (
827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
832 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
833 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
835 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
836 prepare_directory (dir);
837 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
838 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
839 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
840 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
842 check_verify_result (
845 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
846 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
851 BOOST_AUTO_TEST_CASE (verify_external_asset)
853 path const ov_dir("build/test/verify_external_asset");
854 prepare_directory (ov_dir);
856 auto image = black_image ();
857 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
858 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
859 dcp_from_frame (frame, ov_dir);
861 dcp::DCP ov (ov_dir);
864 path const vf_dir("build/test/verify_external_asset_vf");
865 prepare_directory (vf_dir);
867 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
868 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
870 check_verify_result (
873 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
874 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
879 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
881 path const dir("build/test/verify_valid_cpl_metadata");
882 prepare_directory (dir);
884 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
885 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
886 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
888 auto reel = make_shared<dcp::Reel>();
889 reel->add (reel_asset);
891 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
892 reel->add (simple_markers(16 * 24));
894 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
896 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
897 cpl->set_main_sound_sample_rate (48000);
898 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
899 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
900 cpl->set_version_number (1);
904 dcp.set_annotation_text("hello");
909 path find_cpl (path dir)
911 for (auto i: directory_iterator(dir)) {
912 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
917 BOOST_REQUIRE (false);
922 /* DCP with invalid CompositionMetadataAsset */
923 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
925 using namespace boost::filesystem;
927 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
928 prepare_directory (dir);
930 auto reel = make_shared<dcp::Reel>();
931 reel->add (black_picture_asset(dir));
932 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
934 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
935 cpl->set_main_sound_sample_rate (48000);
936 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
937 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
938 cpl->set_version_number (1);
940 reel->add (simple_markers());
944 dcp.set_annotation_text("hello");
948 Editor e (find_cpl(dir));
949 e.replace ("MainSound", "MainSoundX");
952 check_verify_result (
955 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
956 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
958 dcp::VerificationNote::Type::ERROR,
959 dcp::VerificationNote::Code::INVALID_XML,
960 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
961 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
962 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
963 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
964 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
965 "ExtensionMetadataList?,)'"),
966 canonical(cpl->file().get()),
969 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
974 /* DCP with invalid CompositionMetadataAsset */
975 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
977 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
978 prepare_directory (dir);
980 auto reel = make_shared<dcp::Reel>();
981 reel->add (black_picture_asset(dir));
982 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
984 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
985 cpl->set_main_sound_sample_rate (48000);
986 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
987 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
991 dcp.set_annotation_text("hello");
995 Editor e (find_cpl(dir));
996 e.replace ("meta:Width", "meta:WidthX");
999 check_verify_result (
1001 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1006 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1008 path const dir("build/test/verify_invalid_language1");
1009 prepare_directory (dir);
1010 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1011 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1012 asset->_language = "wrong-andbad";
1013 asset->write (dir / "subs.mxf");
1014 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1015 reel_asset->_language = "badlang";
1016 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1018 check_verify_result (
1021 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1022 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1023 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1028 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1029 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1031 path const dir("build/test/verify_invalid_language2");
1032 prepare_directory (dir);
1033 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1034 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1035 asset->_language = "wrong-andbad";
1036 asset->write (dir / "subs.mxf");
1037 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1038 reel_asset->_language = "badlang";
1039 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1041 check_verify_result (
1044 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1046 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1051 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1052 * the release territory.
1054 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1056 path const dir("build/test/verify_invalid_language3");
1057 prepare_directory (dir);
1059 auto picture = simple_picture (dir, "foo");
1060 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1061 auto reel = make_shared<dcp::Reel>();
1062 reel->add (reel_picture);
1063 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1064 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1065 reel->add (reel_sound);
1066 reel->add (simple_markers());
1068 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1070 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1071 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1072 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1073 cpl->set_main_sound_sample_rate (48000);
1074 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1075 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1076 cpl->set_version_number (1);
1077 cpl->_release_territory = "fred-jim";
1078 auto dcp = make_shared<dcp::DCP>(dir);
1080 dcp->set_annotation_text("hello");
1083 check_verify_result (
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1089 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1095 vector<dcp::VerificationNote>
1096 check_picture_size (int width, int height, int frame_rate, bool three_d)
1098 using namespace boost::filesystem;
1100 path dcp_path = "build/test/verify_picture_test";
1101 prepare_directory (dcp_path);
1103 shared_ptr<dcp::PictureAsset> mp;
1105 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1107 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1109 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1111 auto image = black_image (dcp::Size(width, height));
1112 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1113 int const length = three_d ? frame_rate * 2 : frame_rate;
1114 for (int i = 0; i < length; ++i) {
1115 picture_writer->write (j2c.data(), j2c.size());
1117 picture_writer->finalize ();
1119 auto d = make_shared<dcp::DCP>(dcp_path);
1120 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1121 cpl->set_annotation_text ("A Test DCP");
1122 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1123 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1124 cpl->set_main_sound_sample_rate (48000);
1125 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1126 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1127 cpl->set_version_number (1);
1129 auto reel = make_shared<dcp::Reel>();
1132 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1134 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1137 reel->add (simple_markers(frame_rate));
1142 d->set_annotation_text("A Test DCP");
1145 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1151 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1153 auto notes = check_picture_size(width, height, frame_rate, three_d);
1154 BOOST_CHECK_EQUAL (notes.size(), 0U);
1160 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1162 auto notes = check_picture_size(width, height, frame_rate, three_d);
1163 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1164 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1165 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1171 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1173 auto notes = check_picture_size(width, height, frame_rate, three_d);
1174 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1175 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1176 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1182 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1184 auto notes = check_picture_size(width, height, frame_rate, three_d);
1185 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1186 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1187 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1191 BOOST_AUTO_TEST_CASE (verify_picture_size)
1193 using namespace boost::filesystem;
1196 check_picture_size_ok (2048, 858, 24, false);
1197 check_picture_size_ok (2048, 858, 25, false);
1198 check_picture_size_ok (2048, 858, 48, false);
1199 check_picture_size_ok (2048, 858, 24, true);
1200 check_picture_size_ok (2048, 858, 25, true);
1201 check_picture_size_ok (2048, 858, 48, true);
1204 check_picture_size_ok (1998, 1080, 24, false);
1205 check_picture_size_ok (1998, 1080, 25, false);
1206 check_picture_size_ok (1998, 1080, 48, false);
1207 check_picture_size_ok (1998, 1080, 24, true);
1208 check_picture_size_ok (1998, 1080, 25, true);
1209 check_picture_size_ok (1998, 1080, 48, true);
1212 check_picture_size_ok (4096, 1716, 24, false);
1215 check_picture_size_ok (3996, 2160, 24, false);
1217 /* Bad frame size */
1218 check_picture_size_bad_frame_size (2050, 858, 24, false);
1219 check_picture_size_bad_frame_size (2048, 658, 25, false);
1220 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1221 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1223 /* Bad 2K frame rate */
1224 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1225 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1226 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1228 /* Bad 4K frame rate */
1229 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1230 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1233 auto notes = check_picture_size(3996, 2160, 24, true);
1234 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1235 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1236 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1242 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")
1245 make_shared<dcp::SubtitleString>(
1253 dcp::Time(start_frame, 24, 24),
1254 dcp::Time(end_frame, 24, 24),
1256 dcp::HAlign::CENTER,
1259 dcp::Direction::LTR,
1271 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1273 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1274 prepare_directory (dir);
1276 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1277 for (int i = 0; i < 2048; ++i) {
1278 add_test_subtitle (asset, i * 24, i * 24 + 20);
1280 asset->set_language (dcp::LanguageTag("de-DE"));
1281 asset->write (dir / "subs.mxf");
1282 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1283 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1285 check_verify_result (
1288 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1290 dcp::VerificationNote::Type::BV21_ERROR,
1291 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1293 canonical(dir / "subs.mxf")
1295 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1296 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1302 shared_ptr<dcp::SMPTESubtitleAsset>
1303 make_large_subtitle_asset (path font_file)
1305 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1306 dcp::ArrayData big_fake_font(1024 * 1024);
1307 big_fake_font.write (font_file);
1308 for (int i = 0; i < 116; ++i) {
1309 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1317 verify_timed_text_asset_too_large (string name)
1319 auto const dir = path("build/test") / name;
1320 prepare_directory (dir);
1321 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1322 add_test_subtitle (asset, 0, 240);
1323 asset->set_language (dcp::LanguageTag("de-DE"));
1324 asset->write (dir / "subs.mxf");
1326 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1327 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1329 check_verify_result (
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1335 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1341 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1343 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1344 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1348 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1350 path dir = "build/test/verify_missing_subtitle_language";
1351 prepare_directory (dir);
1352 auto dcp = make_simple (dir, 1, 106);
1355 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1356 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1357 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1358 "<ContentTitleText>Content</ContentTitleText>"
1359 "<AnnotationText>Annotation</AnnotationText>"
1360 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1361 "<ReelNumber>1</ReelNumber>"
1362 "<EditRate>24 1</EditRate>"
1363 "<TimeCodeRate>24</TimeCodeRate>"
1364 "<StartTime>00:00:00:00</StartTime>"
1365 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1367 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1368 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1369 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1375 dcp::File xml_file(dir / "subs.xml", "w");
1376 BOOST_REQUIRE (xml_file);
1377 xml_file.write(xml.c_str(), xml.size(), 1);
1379 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1380 subs->write (dir / "subs.mxf");
1382 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1383 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1384 dcp->set_annotation_text("A Test DCP");
1387 check_verify_result (
1390 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1391 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1396 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1398 path path ("build/test/verify_mismatched_subtitle_languages");
1399 auto constexpr reel_length = 192;
1400 auto dcp = make_simple (path, 2, reel_length);
1401 auto cpl = dcp->cpls()[0];
1404 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1405 subs->set_language (dcp::LanguageTag("de-DE"));
1406 subs->add (simple_subtitle());
1407 subs->write (path / "subs1.mxf");
1408 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1409 cpl->reels()[0]->add(reel_subs);
1413 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1414 subs->set_language (dcp::LanguageTag("en-US"));
1415 subs->add (simple_subtitle());
1416 subs->write (path / "subs2.mxf");
1417 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1418 cpl->reels()[1]->add(reel_subs);
1421 dcp->set_annotation_text("A Test DCP");
1424 check_verify_result (
1427 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1428 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1429 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1434 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1436 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1437 auto constexpr reel_length = 192;
1438 auto dcp = make_simple (path, 2, reel_length);
1439 auto cpl = dcp->cpls()[0];
1442 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1443 ccaps->set_language (dcp::LanguageTag("de-DE"));
1444 ccaps->add (simple_subtitle());
1445 ccaps->write (path / "subs1.mxf");
1446 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1447 cpl->reels()[0]->add(reel_ccaps);
1451 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1452 ccaps->set_language (dcp::LanguageTag("en-US"));
1453 ccaps->add (simple_subtitle());
1454 ccaps->write (path / "subs2.mxf");
1455 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1456 cpl->reels()[1]->add(reel_ccaps);
1459 dcp->set_annotation_text("A Test DCP");
1462 check_verify_result (
1465 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1466 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1471 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1473 path dir = "build/test/verify_missing_subtitle_start_time";
1474 prepare_directory (dir);
1475 auto dcp = make_simple (dir, 1, 106);
1478 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1479 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1480 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1481 "<ContentTitleText>Content</ContentTitleText>"
1482 "<AnnotationText>Annotation</AnnotationText>"
1483 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1484 "<ReelNumber>1</ReelNumber>"
1485 "<Language>de-DE</Language>"
1486 "<EditRate>24 1</EditRate>"
1487 "<TimeCodeRate>24</TimeCodeRate>"
1488 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1490 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1491 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1492 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1498 dcp::File xml_file(dir / "subs.xml", "w");
1499 BOOST_REQUIRE (xml_file);
1500 xml_file.write(xml.c_str(), xml.size(), 1);
1502 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1503 subs->write (dir / "subs.mxf");
1505 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1506 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1507 dcp->set_annotation_text("A Test DCP");
1510 check_verify_result (
1513 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1514 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1519 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1521 path dir = "build/test/verify_invalid_subtitle_start_time";
1522 prepare_directory (dir);
1523 auto dcp = make_simple (dir, 1, 106);
1526 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1527 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1528 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1529 "<ContentTitleText>Content</ContentTitleText>"
1530 "<AnnotationText>Annotation</AnnotationText>"
1531 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1532 "<ReelNumber>1</ReelNumber>"
1533 "<Language>de-DE</Language>"
1534 "<EditRate>24 1</EditRate>"
1535 "<TimeCodeRate>24</TimeCodeRate>"
1536 "<StartTime>00:00:02:00</StartTime>"
1537 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1539 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1540 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1541 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1547 dcp::File xml_file(dir / "subs.xml", "w");
1548 BOOST_REQUIRE (xml_file);
1549 xml_file.write(xml.c_str(), xml.size(), 1);
1551 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1552 subs->write (dir / "subs.mxf");
1554 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1555 dcp->cpls().front()->reels().front()->add(reel_subs);
1556 dcp->set_annotation_text("A Test DCP");
1559 check_verify_result (
1562 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1563 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1571 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1574 , v_position(v_position_)
1582 dcp::VAlign v_align;
1588 shared_ptr<dcp::CPL>
1589 dcp_with_text (path dir, vector<TestText> subs)
1591 prepare_directory (dir);
1592 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1593 asset->set_start_time (dcp::Time());
1594 for (auto i: subs) {
1595 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1597 asset->set_language (dcp::LanguageTag("de-DE"));
1598 asset->write (dir / "subs.mxf");
1600 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1601 return write_dcp_with_single_asset (dir, reel_asset);
1606 shared_ptr<dcp::CPL>
1607 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1609 prepare_directory (dir);
1610 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1611 asset->set_start_time (dcp::Time());
1612 asset->set_language (dcp::LanguageTag("de-DE"));
1614 auto subs_mxf = dir / "subs.mxf";
1615 asset->write (subs_mxf);
1617 /* The call to write() puts the asset into the DCP correctly but it will have
1618 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1621 ASDCP::TimedText::MXFWriter writer;
1622 ASDCP::WriterInfo writer_info;
1623 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1625 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1626 DCP_ASSERT (c == Kumu::UUID_Length);
1627 ASDCP::TimedText::TimedTextDescriptor descriptor;
1628 descriptor.ContainerDuration = asset->intrinsic_duration();
1629 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1630 DCP_ASSERT (c == Kumu::UUID_Length);
1631 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1632 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1633 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1634 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1637 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1638 return write_dcp_with_single_asset (dir, reel_asset);
1642 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1644 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1645 /* Just too early */
1646 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1647 check_verify_result (
1650 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1651 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1657 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1659 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1660 /* Just late enough */
1661 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1662 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1666 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1668 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1669 prepare_directory (dir);
1671 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1672 asset1->set_start_time (dcp::Time());
1673 /* Just late enough */
1674 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1675 asset1->set_language (dcp::LanguageTag("de-DE"));
1676 asset1->write (dir / "subs1.mxf");
1677 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1678 auto reel1 = make_shared<dcp::Reel>();
1679 reel1->add (reel_asset1);
1680 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1681 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1682 reel1->add (markers1);
1684 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1685 asset2->set_start_time (dcp::Time());
1686 /* This would be too early on first reel but should be OK on the second */
1687 add_test_subtitle (asset2, 3, 4 * 24);
1688 asset2->set_language (dcp::LanguageTag("de-DE"));
1689 asset2->write (dir / "subs2.mxf");
1690 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1691 auto reel2 = make_shared<dcp::Reel>();
1692 reel2->add (reel_asset2);
1693 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1694 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1695 reel2->add (markers2);
1697 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1700 auto dcp = make_shared<dcp::DCP>(dir);
1702 dcp->set_annotation_text("hello");
1705 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1709 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1711 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1712 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1716 { 5 * 24 + 1, 6 * 24 },
1718 check_verify_result (
1721 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1727 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1729 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1730 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1734 { 5 * 24 + 16, 8 * 24 },
1736 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1740 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1742 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1743 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1744 check_verify_result (
1747 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1748 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1753 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1755 auto const dir = path("build/test/verify_valid_subtitle_duration");
1756 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1757 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1761 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1763 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1764 prepare_directory (dir);
1765 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1766 asset->set_start_time (dcp::Time());
1767 add_test_subtitle (asset, 0, 4 * 24);
1768 asset->set_language (dcp::LanguageTag("de-DE"));
1769 asset->write (dir / "subs.mxf");
1771 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1772 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1773 check_verify_result (
1776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1777 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1778 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1779 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1785 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1787 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1788 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1791 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1792 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1793 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1794 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1796 check_verify_result (
1799 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1805 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1807 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1808 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1811 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1812 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1813 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1815 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1819 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1821 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1822 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1825 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1826 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1827 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1828 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1830 check_verify_result (
1833 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1834 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1839 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1841 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1842 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1845 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1846 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1847 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1848 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1850 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1854 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1856 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1857 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1860 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1862 check_verify_result (
1865 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1866 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1871 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1873 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1874 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1877 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1879 check_verify_result (
1882 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1888 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1890 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1891 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1894 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1895 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1896 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1897 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1899 check_verify_result (
1902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1903 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1908 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1910 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1911 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1914 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1915 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1916 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1918 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1922 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1924 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1925 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1928 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1929 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1930 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1931 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1933 check_verify_result (
1936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1942 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1944 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1945 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1948 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1949 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1950 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1951 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1953 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1957 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1959 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1960 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1963 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1965 check_verify_result (
1968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1973 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1975 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1976 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1979 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
1981 check_verify_result (
1984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1990 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
1992 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
1993 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1996 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
1997 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
1998 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2000 check_verify_result (
2003 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2008 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2010 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2011 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2014 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2015 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2016 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2018 check_verify_result (
2021 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2022 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2027 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2029 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2030 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2033 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2034 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2035 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2037 check_verify_result (
2040 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2045 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2047 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2048 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2051 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2052 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2053 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2055 check_verify_result (
2058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2063 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2065 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2066 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2067 check_verify_result (
2070 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2071 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2076 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2078 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2079 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2080 check_verify_result (
2083 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2089 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2091 path const dir("build/test/verify_invalid_sound_frame_rate");
2092 prepare_directory (dir);
2094 auto picture = simple_picture (dir, "foo");
2095 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2096 auto reel = make_shared<dcp::Reel>();
2097 reel->add (reel_picture);
2098 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2099 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2100 reel->add (reel_sound);
2101 reel->add (simple_markers());
2102 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2104 auto dcp = make_shared<dcp::DCP>(dir);
2106 dcp->set_annotation_text("hello");
2109 check_verify_result (
2112 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2118 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2120 path const dir("build/test/verify_missing_cpl_annotation_text");
2121 auto dcp = make_simple (dir);
2122 dcp->set_annotation_text("A Test DCP");
2125 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2127 auto const cpl = dcp->cpls()[0];
2130 BOOST_REQUIRE (cpl->file());
2131 Editor e(cpl->file().get());
2132 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2135 check_verify_result (
2138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2139 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2144 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2146 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2147 auto dcp = make_simple (dir);
2148 dcp->set_annotation_text("A Test DCP");
2151 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2152 auto const cpl = dcp->cpls()[0];
2155 BOOST_REQUIRE (cpl->file());
2156 Editor e(cpl->file().get());
2157 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2160 check_verify_result (
2163 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2164 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2169 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2171 path const dir("build/test/verify_mismatched_asset_duration");
2172 prepare_directory (dir);
2173 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2174 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2176 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2177 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2179 auto reel = make_shared<dcp::Reel>(
2180 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2181 make_shared<dcp::ReelSoundAsset>(ms, 0)
2184 reel->add (simple_markers());
2188 dcp->set_annotation_text("A Test DCP");
2191 check_verify_result (
2194 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2195 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2202 shared_ptr<dcp::CPL>
2203 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2205 prepare_directory (dir);
2206 auto dcp = make_shared<dcp::DCP>(dir);
2207 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2209 auto constexpr reel_length = 192;
2211 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2212 subs->set_language (dcp::LanguageTag("de-DE"));
2213 subs->set_start_time (dcp::Time());
2214 subs->add (simple_subtitle());
2215 subs->write (dir / "subs.mxf");
2216 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2218 auto reel1 = make_shared<dcp::Reel>(
2219 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2220 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2224 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2227 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2228 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2229 reel1->add (markers1);
2233 auto reel2 = make_shared<dcp::Reel>(
2234 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2235 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2239 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2242 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2243 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2244 reel2->add (markers2);
2249 dcp->set_annotation_text("A Test DCP");
2256 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2259 path dir ("build/test/missing_main_subtitle_from_some_reels");
2260 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2261 check_verify_result (
2264 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2271 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2272 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2273 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2277 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2278 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2279 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2285 shared_ptr<dcp::CPL>
2286 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2288 prepare_directory (dir);
2289 auto dcp = make_shared<dcp::DCP>(dir);
2290 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2292 auto constexpr reel_length = 192;
2294 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2295 subs->set_language (dcp::LanguageTag("de-DE"));
2296 subs->set_start_time (dcp::Time());
2297 subs->add (simple_subtitle());
2298 subs->write (dir / "subs.mxf");
2300 auto reel1 = make_shared<dcp::Reel>(
2301 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2302 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2305 for (int i = 0; i < caps_in_reel1; ++i) {
2306 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2309 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2310 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2311 reel1->add (markers1);
2315 auto reel2 = make_shared<dcp::Reel>(
2316 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2317 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2320 for (int i = 0; i < caps_in_reel2; ++i) {
2321 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2324 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2325 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2326 reel2->add (markers2);
2331 dcp->set_annotation_text("A Test DCP");
2338 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2341 path dir ("build/test/mismatched_closed_caption_asset_counts");
2342 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2343 check_verify_result (
2346 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2347 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2352 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2353 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2354 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2358 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2359 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2360 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2367 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2369 prepare_directory (dir);
2370 auto dcp = make_shared<dcp::DCP>(dir);
2371 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2373 auto constexpr reel_length = 192;
2375 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2376 subs->set_language (dcp::LanguageTag("de-DE"));
2377 subs->set_start_time (dcp::Time());
2378 subs->add (simple_subtitle());
2379 subs->write (dir / "subs.mxf");
2380 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2383 auto reel = make_shared<dcp::Reel>(
2384 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2385 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2388 reel->add (reel_text);
2390 reel->add (simple_markers(reel_length));
2395 dcp->set_annotation_text("A Test DCP");
2398 check_verify_result (
2401 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2402 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2407 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2409 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2410 "build/test/verify_subtitle_entry_point_must_be_present",
2411 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2412 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2413 asset->unset_entry_point ();
2417 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2418 "build/test/verify_subtitle_entry_point_must_be_zero",
2419 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2420 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2421 asset->set_entry_point (4);
2425 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2426 "build/test/verify_closed_caption_entry_point_must_be_present",
2427 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2428 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2429 asset->unset_entry_point ();
2433 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2434 "build/test/verify_closed_caption_entry_point_must_be_zero",
2435 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2436 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2437 asset->set_entry_point (9);
2443 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2447 path const dir("build/test/verify_missing_hash");
2448 auto dcp = make_simple (dir);
2449 dcp->set_annotation_text("A Test DCP");
2452 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2453 auto const cpl = dcp->cpls()[0];
2454 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2455 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2456 auto asset_id = cpl->reels()[0]->main_picture()->id();
2459 BOOST_REQUIRE (cpl->file());
2460 Editor e(cpl->file().get());
2461 e.delete_first_line_containing("<Hash>");
2464 check_verify_result (
2467 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2468 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2475 verify_markers_test (
2477 vector<pair<dcp::Marker, dcp::Time>> markers,
2478 vector<dcp::VerificationNote> test_notes
2481 auto dcp = make_simple (dir);
2482 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2483 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2484 for (auto const& i: markers) {
2485 markers_asset->set (i.first, i.second);
2487 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2488 dcp->set_annotation_text("A Test DCP");
2491 check_verify_result ({dir}, test_notes);
2495 BOOST_AUTO_TEST_CASE (verify_markers)
2497 verify_markers_test (
2498 "build/test/verify_markers_all_correct",
2500 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2501 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2502 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2503 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2508 verify_markers_test (
2509 "build/test/verify_markers_missing_ffec",
2511 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2512 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2513 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2516 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2519 verify_markers_test (
2520 "build/test/verify_markers_missing_ffmc",
2522 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2523 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2524 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2527 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2530 verify_markers_test (
2531 "build/test/verify_markers_missing_ffoc",
2533 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2534 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2535 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2538 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2541 verify_markers_test (
2542 "build/test/verify_markers_missing_lfoc",
2544 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2545 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2546 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2549 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2552 verify_markers_test (
2553 "build/test/verify_markers_incorrect_ffoc",
2555 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2556 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2557 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2558 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2561 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2564 verify_markers_test (
2565 "build/test/verify_markers_incorrect_lfoc",
2567 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2568 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2569 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2570 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2573 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2578 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2580 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2581 prepare_directory (dir);
2582 auto dcp = make_simple (dir);
2583 auto cpl = dcp->cpls()[0];
2584 cpl->unset_version_number();
2585 dcp->set_annotation_text("A Test DCP");
2588 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2592 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2594 path dir = "build/test/verify_missing_extension_metadata1";
2595 auto dcp = make_simple (dir);
2596 dcp->set_annotation_text("A Test DCP");
2599 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2600 auto cpl = dcp->cpls()[0];
2603 Editor e (cpl->file().get());
2604 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2607 check_verify_result (
2610 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2611 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2616 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2618 path dir = "build/test/verify_missing_extension_metadata2";
2619 auto dcp = make_simple (dir);
2620 dcp->set_annotation_text("A Test DCP");
2623 auto cpl = dcp->cpls()[0];
2626 Editor e (cpl->file().get());
2627 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2630 check_verify_result (
2633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2634 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2639 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2641 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2642 auto dcp = make_simple (dir);
2643 dcp->set_annotation_text("A Test DCP");
2646 auto const cpl = dcp->cpls()[0];
2649 Editor e (cpl->file().get());
2650 e.replace ("<meta:Name>A", "<meta:NameX>A");
2651 e.replace ("n</meta:Name>", "n</meta:NameX>");
2654 check_verify_result (
2657 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2658 { 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 },
2659 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2664 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2666 path dir = "build/test/verify_invalid_extension_metadata1";
2667 auto dcp = make_simple (dir);
2668 dcp->set_annotation_text("A Test DCP");
2671 auto cpl = dcp->cpls()[0];
2674 Editor e (cpl->file().get());
2675 e.replace ("Application", "Fred");
2678 check_verify_result (
2681 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2682 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2687 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2689 path dir = "build/test/verify_invalid_extension_metadata2";
2690 auto dcp = make_simple (dir);
2691 dcp->set_annotation_text("A Test DCP");
2694 auto cpl = dcp->cpls()[0];
2697 Editor e (cpl->file().get());
2698 e.replace ("DCP Constraints Profile", "Fred");
2701 check_verify_result (
2704 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2710 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2712 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2713 auto dcp = make_simple (dir);
2714 dcp->set_annotation_text("A Test DCP");
2717 auto const cpl = dcp->cpls()[0];
2720 Editor e (cpl->file().get());
2721 e.replace ("<meta:Value>", "<meta:ValueX>");
2722 e.replace ("</meta:Value>", "</meta:ValueX>");
2725 check_verify_result (
2728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2729 { 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 },
2730 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2735 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2737 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2738 auto dcp = make_simple (dir);
2739 dcp->set_annotation_text("A Test DCP");
2742 auto const cpl = dcp->cpls()[0];
2745 Editor e (cpl->file().get());
2746 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2749 check_verify_result (
2752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2753 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2758 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2760 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2761 auto dcp = make_simple (dir);
2762 dcp->set_annotation_text("A Test DCP");
2765 auto const cpl = dcp->cpls()[0];
2768 Editor e (cpl->file().get());
2769 e.replace ("<meta:Property>", "<meta:PropertyX>");
2770 e.replace ("</meta:Property>", "</meta:PropertyX>");
2773 check_verify_result (
2776 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2777 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2778 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2783 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2785 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2786 auto dcp = make_simple (dir);
2787 dcp->set_annotation_text("A Test DCP");
2790 auto const cpl = dcp->cpls()[0];
2793 Editor e (cpl->file().get());
2794 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2795 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2798 check_verify_result (
2801 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2802 { 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 },
2803 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2809 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2811 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2812 prepare_directory (dir);
2813 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2814 copy_file (i.path(), dir / i.path().filename());
2817 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2818 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2822 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2825 check_verify_result (
2828 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2829 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2831 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2832 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2833 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2834 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2840 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2842 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2843 prepare_directory (dir);
2844 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2845 copy_file (i.path(), dir / i.path().filename());
2848 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2849 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2852 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2855 check_verify_result (
2858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2859 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2860 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2861 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2862 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2863 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2869 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2871 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2872 prepare_directory (dir);
2873 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2874 copy_file (i.path(), dir / i.path().filename());
2878 Editor e (dir / dcp_test1_pkl);
2879 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2882 check_verify_result ({dir}, {});
2886 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2888 path dir ("build/test/verify_must_not_be_partially_encrypted");
2889 prepare_directory (dir);
2893 auto signer = make_shared<dcp::CertificateChain>();
2894 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2895 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2896 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2897 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2899 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2903 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2906 auto writer = mp->start_write (dir / "video.mxf", false);
2907 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2908 for (int i = 0; i < 24; ++i) {
2909 writer->write (j2c.data(), j2c.size());
2911 writer->finalize ();
2913 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2915 auto reel = make_shared<dcp::Reel>(
2916 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2917 make_shared<dcp::ReelSoundAsset>(ms, 0)
2920 reel->add (simple_markers());
2924 cpl->set_content_version (
2925 {"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"}
2927 cpl->set_annotation_text ("A Test DCP");
2928 cpl->set_issuer ("OpenDCP 0.0.25");
2929 cpl->set_creator ("OpenDCP 0.0.25");
2930 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2931 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2932 cpl->set_main_sound_sample_rate (48000);
2933 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2934 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2935 cpl->set_version_number (1);
2939 d.set_issuer("OpenDCP 0.0.25");
2940 d.set_creator("OpenDCP 0.0.25");
2941 d.set_issue_date("2012-07-17T04:45:18+00:00");
2942 d.set_annotation_text("A Test DCP");
2943 d.write_xml(signer);
2945 check_verify_result (
2948 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2953 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2955 vector<dcp::VerificationNote> notes;
2956 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"));
2957 auto reader = picture.start_read ();
2958 auto frame = reader->get_frame (0);
2959 verify_j2k (frame, notes);
2960 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2964 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2966 vector<dcp::VerificationNote> notes;
2967 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2968 auto reader = picture.start_read ();
2969 auto frame = reader->get_frame (0);
2970 verify_j2k (frame, notes);
2971 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2975 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2977 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2978 prepare_directory (dir);
2979 auto dcp = make_simple (dir);
2981 vector<dcp::VerificationNote> notes;
2982 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2983 auto reader = picture.start_read ();
2984 auto frame = reader->get_frame (0);
2985 verify_j2k (frame, notes);
2986 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2990 /** Check that ResourceID and the XML ID being different is spotted */
2991 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2993 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2994 prepare_directory (dir);
2996 ASDCP::WriterInfo writer_info;
2997 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3000 auto mxf_id = dcp::make_uuid ();
3001 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3002 BOOST_REQUIRE (c == Kumu::UUID_Length);
3004 auto resource_id = dcp::make_uuid ();
3005 ASDCP::TimedText::TimedTextDescriptor descriptor;
3006 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3007 DCP_ASSERT (c == Kumu::UUID_Length);
3009 auto xml_id = dcp::make_uuid ();
3010 ASDCP::TimedText::MXFWriter writer;
3011 auto subs_mxf = dir / "subs.mxf";
3012 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3013 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3014 writer.WriteTimedTextResource (dcp::String::compose(
3015 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3016 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3017 "<Id>urn:uuid:%1</Id>"
3018 "<ContentTitleText>Content</ContentTitleText>"
3019 "<AnnotationText>Annotation</AnnotationText>"
3020 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3021 "<ReelNumber>1</ReelNumber>"
3022 "<Language>en-US</Language>"
3023 "<EditRate>25 1</EditRate>"
3024 "<TimeCodeRate>25</TimeCodeRate>"
3025 "<StartTime>00:00:00:00</StartTime>"
3027 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3028 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3029 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3038 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3039 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3041 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3043 check_verify_result (
3046 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3047 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3048 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3049 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3054 /** Check that ResourceID and the MXF ID being the same is spotted */
3055 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3057 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3058 prepare_directory (dir);
3060 ASDCP::WriterInfo writer_info;
3061 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3064 auto mxf_id = dcp::make_uuid ();
3065 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3066 BOOST_REQUIRE (c == Kumu::UUID_Length);
3068 auto resource_id = mxf_id;
3069 ASDCP::TimedText::TimedTextDescriptor descriptor;
3070 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3071 DCP_ASSERT (c == Kumu::UUID_Length);
3073 auto xml_id = resource_id;
3074 ASDCP::TimedText::MXFWriter writer;
3075 auto subs_mxf = dir / "subs.mxf";
3076 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3077 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3078 writer.WriteTimedTextResource (dcp::String::compose(
3079 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3080 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3081 "<Id>urn:uuid:%1</Id>"
3082 "<ContentTitleText>Content</ContentTitleText>"
3083 "<AnnotationText>Annotation</AnnotationText>"
3084 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3085 "<ReelNumber>1</ReelNumber>"
3086 "<Language>en-US</Language>"
3087 "<EditRate>25 1</EditRate>"
3088 "<TimeCodeRate>25</TimeCodeRate>"
3089 "<StartTime>00:00:00:00</StartTime>"
3091 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3092 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3093 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3102 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3103 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3105 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3107 check_verify_result (
3110 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3112 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3118 /** Check a DCP with a 3D asset marked as 2D */
3119 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3121 check_verify_result (
3122 { private_test / "data" / "xm" },
3125 dcp::VerificationNote::Type::WARNING,
3126 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3129 dcp::VerificationNote::Type::BV21_ERROR,
3130 dcp::VerificationNote::Code::INVALID_STANDARD
3137 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3139 path dir = "build/test/verify_unexpected_things_in_main_markers";
3140 prepare_directory (dir);
3141 auto dcp = make_simple (dir, 1, 24);
3142 dcp->set_annotation_text("A Test DCP");
3146 Editor e (find_cpl(dir));
3148 " <IntrinsicDuration>24</IntrinsicDuration>",
3149 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3153 dcp::CPL cpl (find_cpl(dir));
3155 check_verify_result (
3158 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3159 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3160 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3165 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3167 path dir = "build/test/verify_invalid_content_kind";
3168 prepare_directory (dir);
3169 auto dcp = make_simple (dir, 1, 24);
3170 dcp->set_annotation_text("A Test DCP");
3174 Editor e(find_cpl(dir));
3175 e.replace("trailer", "trip");
3178 dcp::CPL cpl (find_cpl(dir));
3180 check_verify_result (
3183 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3184 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3190 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3192 path dir = "build/test/verify_valid_content_kind";
3193 prepare_directory (dir);
3194 auto dcp = make_simple (dir, 1, 24);
3195 dcp->set_annotation_text("A Test DCP");
3199 Editor e(find_cpl(dir));
3200 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3203 dcp::CPL cpl (find_cpl(dir));
3205 check_verify_result (
3208 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },