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 std::make_shared<dcp::SubtitleString>(
1253 dcp::Time(start_frame, 24, 24),
1254 dcp::Time(end_frame, 24, 24),
1256 dcp::HAlign::CENTER,
1260 dcp::Direction::LTR,
1272 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1274 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1275 prepare_directory (dir);
1277 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1278 for (int i = 0; i < 2048; ++i) {
1279 add_test_subtitle (asset, i * 24, i * 24 + 20);
1281 asset->set_language (dcp::LanguageTag("de-DE"));
1282 asset->write (dir / "subs.mxf");
1283 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1284 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1286 check_verify_result (
1289 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1291 dcp::VerificationNote::Type::BV21_ERROR,
1292 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1294 canonical(dir / "subs.mxf")
1296 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1297 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1303 shared_ptr<dcp::SMPTESubtitleAsset>
1304 make_large_subtitle_asset (path font_file)
1306 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1307 dcp::ArrayData big_fake_font(1024 * 1024);
1308 big_fake_font.write (font_file);
1309 for (int i = 0; i < 116; ++i) {
1310 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1318 verify_timed_text_asset_too_large (string name)
1320 auto const dir = path("build/test") / name;
1321 prepare_directory (dir);
1322 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1323 add_test_subtitle (asset, 0, 240);
1324 asset->set_language (dcp::LanguageTag("de-DE"));
1325 asset->write (dir / "subs.mxf");
1327 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1328 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1330 check_verify_result (
1333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1336 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1342 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1344 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1345 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1349 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1351 path dir = "build/test/verify_missing_subtitle_language";
1352 prepare_directory (dir);
1353 auto dcp = make_simple (dir, 1, 106);
1356 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1357 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1358 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1359 "<ContentTitleText>Content</ContentTitleText>"
1360 "<AnnotationText>Annotation</AnnotationText>"
1361 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1362 "<ReelNumber>1</ReelNumber>"
1363 "<EditRate>24 1</EditRate>"
1364 "<TimeCodeRate>24</TimeCodeRate>"
1365 "<StartTime>00:00:00:00</StartTime>"
1366 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1368 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1369 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1370 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1376 dcp::File xml_file(dir / "subs.xml", "w");
1377 BOOST_REQUIRE (xml_file);
1378 xml_file.write(xml.c_str(), xml.size(), 1);
1380 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1381 subs->write (dir / "subs.mxf");
1383 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1384 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1385 dcp->set_annotation_text("A Test DCP");
1388 check_verify_result (
1391 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1392 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1397 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1399 path path ("build/test/verify_mismatched_subtitle_languages");
1400 auto constexpr reel_length = 192;
1401 auto dcp = make_simple (path, 2, reel_length);
1402 auto cpl = dcp->cpls()[0];
1405 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1406 subs->set_language (dcp::LanguageTag("de-DE"));
1407 subs->add (simple_subtitle());
1408 subs->write (path / "subs1.mxf");
1409 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1410 cpl->reels()[0]->add(reel_subs);
1414 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1415 subs->set_language (dcp::LanguageTag("en-US"));
1416 subs->add (simple_subtitle());
1417 subs->write (path / "subs2.mxf");
1418 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1419 cpl->reels()[1]->add(reel_subs);
1422 dcp->set_annotation_text("A Test DCP");
1425 check_verify_result (
1428 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1429 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1430 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1435 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1437 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1438 auto constexpr reel_length = 192;
1439 auto dcp = make_simple (path, 2, reel_length);
1440 auto cpl = dcp->cpls()[0];
1443 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1444 ccaps->set_language (dcp::LanguageTag("de-DE"));
1445 ccaps->add (simple_subtitle());
1446 ccaps->write (path / "subs1.mxf");
1447 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1448 cpl->reels()[0]->add(reel_ccaps);
1452 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1453 ccaps->set_language (dcp::LanguageTag("en-US"));
1454 ccaps->add (simple_subtitle());
1455 ccaps->write (path / "subs2.mxf");
1456 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1457 cpl->reels()[1]->add(reel_ccaps);
1460 dcp->set_annotation_text("A Test DCP");
1463 check_verify_result (
1466 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1467 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1472 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1474 path dir = "build/test/verify_missing_subtitle_start_time";
1475 prepare_directory (dir);
1476 auto dcp = make_simple (dir, 1, 106);
1479 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1480 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1481 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1482 "<ContentTitleText>Content</ContentTitleText>"
1483 "<AnnotationText>Annotation</AnnotationText>"
1484 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1485 "<ReelNumber>1</ReelNumber>"
1486 "<Language>de-DE</Language>"
1487 "<EditRate>24 1</EditRate>"
1488 "<TimeCodeRate>24</TimeCodeRate>"
1489 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1491 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1492 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1493 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1499 dcp::File xml_file(dir / "subs.xml", "w");
1500 BOOST_REQUIRE (xml_file);
1501 xml_file.write(xml.c_str(), xml.size(), 1);
1503 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1504 subs->write (dir / "subs.mxf");
1506 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1507 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1508 dcp->set_annotation_text("A Test DCP");
1511 check_verify_result (
1514 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1515 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1520 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1522 path dir = "build/test/verify_invalid_subtitle_start_time";
1523 prepare_directory (dir);
1524 auto dcp = make_simple (dir, 1, 106);
1527 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1528 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1529 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1530 "<ContentTitleText>Content</ContentTitleText>"
1531 "<AnnotationText>Annotation</AnnotationText>"
1532 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1533 "<ReelNumber>1</ReelNumber>"
1534 "<Language>de-DE</Language>"
1535 "<EditRate>24 1</EditRate>"
1536 "<TimeCodeRate>24</TimeCodeRate>"
1537 "<StartTime>00:00:02:00</StartTime>"
1538 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1540 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1541 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1542 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1548 dcp::File xml_file(dir / "subs.xml", "w");
1549 BOOST_REQUIRE (xml_file);
1550 xml_file.write(xml.c_str(), xml.size(), 1);
1552 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1553 subs->write (dir / "subs.mxf");
1555 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1556 dcp->cpls().front()->reels().front()->add(reel_subs);
1557 dcp->set_annotation_text("A Test DCP");
1560 check_verify_result (
1563 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1564 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1572 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1575 , v_position(v_position_)
1583 dcp::VAlign v_align;
1589 shared_ptr<dcp::CPL>
1590 dcp_with_text (path dir, vector<TestText> subs)
1592 prepare_directory (dir);
1593 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1594 asset->set_start_time (dcp::Time());
1595 for (auto i: subs) {
1596 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1598 asset->set_language (dcp::LanguageTag("de-DE"));
1599 asset->write (dir / "subs.mxf");
1601 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1602 return write_dcp_with_single_asset (dir, reel_asset);
1607 shared_ptr<dcp::CPL>
1608 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1610 prepare_directory (dir);
1611 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1612 asset->set_start_time (dcp::Time());
1613 asset->set_language (dcp::LanguageTag("de-DE"));
1615 auto subs_mxf = dir / "subs.mxf";
1616 asset->write (subs_mxf);
1618 /* The call to write() puts the asset into the DCP correctly but it will have
1619 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1622 ASDCP::TimedText::MXFWriter writer;
1623 ASDCP::WriterInfo writer_info;
1624 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1626 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1627 DCP_ASSERT (c == Kumu::UUID_Length);
1628 ASDCP::TimedText::TimedTextDescriptor descriptor;
1629 descriptor.ContainerDuration = asset->intrinsic_duration();
1630 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1631 DCP_ASSERT (c == Kumu::UUID_Length);
1632 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1633 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1634 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1635 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1638 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1639 return write_dcp_with_single_asset (dir, reel_asset);
1643 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1645 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1646 /* Just too early */
1647 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1648 check_verify_result (
1651 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1652 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1658 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1660 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1661 /* Just late enough */
1662 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1663 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1667 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1669 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1670 prepare_directory (dir);
1672 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1673 asset1->set_start_time (dcp::Time());
1674 /* Just late enough */
1675 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1676 asset1->set_language (dcp::LanguageTag("de-DE"));
1677 asset1->write (dir / "subs1.mxf");
1678 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1679 auto reel1 = make_shared<dcp::Reel>();
1680 reel1->add (reel_asset1);
1681 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1682 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1683 reel1->add (markers1);
1685 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1686 asset2->set_start_time (dcp::Time());
1687 /* This would be too early on first reel but should be OK on the second */
1688 add_test_subtitle (asset2, 3, 4 * 24);
1689 asset2->set_language (dcp::LanguageTag("de-DE"));
1690 asset2->write (dir / "subs2.mxf");
1691 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1692 auto reel2 = make_shared<dcp::Reel>();
1693 reel2->add (reel_asset2);
1694 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1695 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1696 reel2->add (markers2);
1698 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1701 auto dcp = make_shared<dcp::DCP>(dir);
1703 dcp->set_annotation_text("hello");
1706 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1710 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1712 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1713 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1717 { 5 * 24 + 1, 6 * 24 },
1719 check_verify_result (
1722 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1728 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1730 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1731 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1735 { 5 * 24 + 16, 8 * 24 },
1737 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1741 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1743 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1744 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1745 check_verify_result (
1748 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1749 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1754 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1756 auto const dir = path("build/test/verify_valid_subtitle_duration");
1757 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1758 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1762 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1764 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1765 prepare_directory (dir);
1766 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1767 asset->set_start_time (dcp::Time());
1768 add_test_subtitle (asset, 0, 4 * 24);
1769 asset->set_language (dcp::LanguageTag("de-DE"));
1770 asset->write (dir / "subs.mxf");
1772 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1773 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1774 check_verify_result (
1777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1778 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1779 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1780 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1786 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1788 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1789 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1792 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1793 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1794 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1795 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1797 check_verify_result (
1800 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1801 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1806 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1808 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1809 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1812 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1813 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1814 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1816 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1820 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1822 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1823 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1826 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1827 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1828 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1829 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1831 check_verify_result (
1834 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1840 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1842 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1843 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1846 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1847 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1848 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1849 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1851 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1855 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1857 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1858 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1861 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1863 check_verify_result (
1866 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1872 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1874 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1875 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1878 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1880 check_verify_result (
1883 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1889 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1891 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1892 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1895 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1896 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1897 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1898 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1900 check_verify_result (
1903 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1904 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1909 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1911 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1912 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1915 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1916 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1917 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1919 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1923 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1925 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1926 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1929 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1930 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1931 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1932 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1934 check_verify_result (
1937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1943 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1945 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1946 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1949 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1950 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1951 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1952 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1954 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1958 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1960 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1961 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1964 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1966 check_verify_result (
1969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1974 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1976 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1977 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1980 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
1982 check_verify_result (
1985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1991 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
1993 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
1994 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1997 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
1998 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
1999 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2001 check_verify_result (
2004 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2009 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2011 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2012 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2015 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2016 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2017 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2019 check_verify_result (
2022 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2023 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2028 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2030 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2031 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2034 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2035 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2036 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2038 check_verify_result (
2041 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2046 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2048 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2049 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2052 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2053 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2054 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2056 check_verify_result (
2059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2064 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2066 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2067 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2068 check_verify_result (
2071 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2072 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2077 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2079 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2080 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2081 check_verify_result (
2084 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2090 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2092 path const dir("build/test/verify_invalid_sound_frame_rate");
2093 prepare_directory (dir);
2095 auto picture = simple_picture (dir, "foo");
2096 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2097 auto reel = make_shared<dcp::Reel>();
2098 reel->add (reel_picture);
2099 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2100 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2101 reel->add (reel_sound);
2102 reel->add (simple_markers());
2103 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2105 auto dcp = make_shared<dcp::DCP>(dir);
2107 dcp->set_annotation_text("hello");
2110 check_verify_result (
2113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2119 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2121 path const dir("build/test/verify_missing_cpl_annotation_text");
2122 auto dcp = make_simple (dir);
2123 dcp->set_annotation_text("A Test DCP");
2126 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2128 auto const cpl = dcp->cpls()[0];
2131 BOOST_REQUIRE (cpl->file());
2132 Editor e(cpl->file().get());
2133 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2136 check_verify_result (
2139 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2140 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2145 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2147 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2148 auto dcp = make_simple (dir);
2149 dcp->set_annotation_text("A Test DCP");
2152 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2153 auto const cpl = dcp->cpls()[0];
2156 BOOST_REQUIRE (cpl->file());
2157 Editor e(cpl->file().get());
2158 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2161 check_verify_result (
2164 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2165 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2170 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2172 path const dir("build/test/verify_mismatched_asset_duration");
2173 prepare_directory (dir);
2174 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2175 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2177 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2178 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2180 auto reel = make_shared<dcp::Reel>(
2181 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2182 make_shared<dcp::ReelSoundAsset>(ms, 0)
2185 reel->add (simple_markers());
2189 dcp->set_annotation_text("A Test DCP");
2192 check_verify_result (
2195 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2196 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2203 shared_ptr<dcp::CPL>
2204 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2206 prepare_directory (dir);
2207 auto dcp = make_shared<dcp::DCP>(dir);
2208 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2210 auto constexpr reel_length = 192;
2212 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2213 subs->set_language (dcp::LanguageTag("de-DE"));
2214 subs->set_start_time (dcp::Time());
2215 subs->add (simple_subtitle());
2216 subs->write (dir / "subs.mxf");
2217 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2219 auto reel1 = make_shared<dcp::Reel>(
2220 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2221 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2225 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2228 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2229 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2230 reel1->add (markers1);
2234 auto reel2 = make_shared<dcp::Reel>(
2235 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2236 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2240 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2243 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2244 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2245 reel2->add (markers2);
2250 dcp->set_annotation_text("A Test DCP");
2257 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2260 path dir ("build/test/missing_main_subtitle_from_some_reels");
2261 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2262 check_verify_result (
2265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2266 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2272 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2273 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2274 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2278 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2279 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2280 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2286 shared_ptr<dcp::CPL>
2287 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2289 prepare_directory (dir);
2290 auto dcp = make_shared<dcp::DCP>(dir);
2291 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2293 auto constexpr reel_length = 192;
2295 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2296 subs->set_language (dcp::LanguageTag("de-DE"));
2297 subs->set_start_time (dcp::Time());
2298 subs->add (simple_subtitle());
2299 subs->write (dir / "subs.mxf");
2301 auto reel1 = make_shared<dcp::Reel>(
2302 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2303 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2306 for (int i = 0; i < caps_in_reel1; ++i) {
2307 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2310 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2311 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2312 reel1->add (markers1);
2316 auto reel2 = make_shared<dcp::Reel>(
2317 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2318 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2321 for (int i = 0; i < caps_in_reel2; ++i) {
2322 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2325 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2326 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2327 reel2->add (markers2);
2332 dcp->set_annotation_text("A Test DCP");
2339 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2342 path dir ("build/test/mismatched_closed_caption_asset_counts");
2343 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2344 check_verify_result (
2347 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2348 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2353 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2354 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2355 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2359 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2360 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2361 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2368 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2370 prepare_directory (dir);
2371 auto dcp = make_shared<dcp::DCP>(dir);
2372 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2374 auto constexpr reel_length = 192;
2376 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2377 subs->set_language (dcp::LanguageTag("de-DE"));
2378 subs->set_start_time (dcp::Time());
2379 subs->add (simple_subtitle());
2380 subs->write (dir / "subs.mxf");
2381 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2384 auto reel = make_shared<dcp::Reel>(
2385 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2386 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2389 reel->add (reel_text);
2391 reel->add (simple_markers(reel_length));
2396 dcp->set_annotation_text("A Test DCP");
2399 check_verify_result (
2402 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2403 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2408 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2410 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2411 "build/test/verify_subtitle_entry_point_must_be_present",
2412 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2413 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2414 asset->unset_entry_point ();
2418 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2419 "build/test/verify_subtitle_entry_point_must_be_zero",
2420 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2421 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2422 asset->set_entry_point (4);
2426 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2427 "build/test/verify_closed_caption_entry_point_must_be_present",
2428 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2429 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2430 asset->unset_entry_point ();
2434 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2435 "build/test/verify_closed_caption_entry_point_must_be_zero",
2436 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2437 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2438 asset->set_entry_point (9);
2444 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2448 path const dir("build/test/verify_missing_hash");
2449 auto dcp = make_simple (dir);
2450 dcp->set_annotation_text("A Test DCP");
2453 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2454 auto const cpl = dcp->cpls()[0];
2455 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2456 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2457 auto asset_id = cpl->reels()[0]->main_picture()->id();
2460 BOOST_REQUIRE (cpl->file());
2461 Editor e(cpl->file().get());
2462 e.delete_first_line_containing("<Hash>");
2465 check_verify_result (
2468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2476 verify_markers_test (
2478 vector<pair<dcp::Marker, dcp::Time>> markers,
2479 vector<dcp::VerificationNote> test_notes
2482 auto dcp = make_simple (dir);
2483 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2484 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2485 for (auto const& i: markers) {
2486 markers_asset->set (i.first, i.second);
2488 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2489 dcp->set_annotation_text("A Test DCP");
2492 check_verify_result ({dir}, test_notes);
2496 BOOST_AUTO_TEST_CASE (verify_markers)
2498 verify_markers_test (
2499 "build/test/verify_markers_all_correct",
2501 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2502 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2503 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2504 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2509 verify_markers_test (
2510 "build/test/verify_markers_missing_ffec",
2512 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2513 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2514 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2517 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2520 verify_markers_test (
2521 "build/test/verify_markers_missing_ffmc",
2523 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2524 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2525 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2528 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2531 verify_markers_test (
2532 "build/test/verify_markers_missing_ffoc",
2534 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2535 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2536 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2539 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2542 verify_markers_test (
2543 "build/test/verify_markers_missing_lfoc",
2545 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2546 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2547 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2550 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2553 verify_markers_test (
2554 "build/test/verify_markers_incorrect_ffoc",
2556 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2557 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2558 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2559 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2562 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2565 verify_markers_test (
2566 "build/test/verify_markers_incorrect_lfoc",
2568 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2569 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2570 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2571 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2574 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2579 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2581 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2582 prepare_directory (dir);
2583 auto dcp = make_simple (dir);
2584 auto cpl = dcp->cpls()[0];
2585 cpl->unset_version_number();
2586 dcp->set_annotation_text("A Test DCP");
2589 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2593 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2595 path dir = "build/test/verify_missing_extension_metadata1";
2596 auto dcp = make_simple (dir);
2597 dcp->set_annotation_text("A Test DCP");
2600 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2601 auto cpl = dcp->cpls()[0];
2604 Editor e (cpl->file().get());
2605 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2608 check_verify_result (
2611 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2612 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2617 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2619 path dir = "build/test/verify_missing_extension_metadata2";
2620 auto dcp = make_simple (dir);
2621 dcp->set_annotation_text("A Test DCP");
2624 auto cpl = dcp->cpls()[0];
2627 Editor e (cpl->file().get());
2628 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2631 check_verify_result (
2634 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2635 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2640 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2642 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2643 auto dcp = make_simple (dir);
2644 dcp->set_annotation_text("A Test DCP");
2647 auto const cpl = dcp->cpls()[0];
2650 Editor e (cpl->file().get());
2651 e.replace ("<meta:Name>A", "<meta:NameX>A");
2652 e.replace ("n</meta:Name>", "n</meta:NameX>");
2655 check_verify_result (
2658 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2659 { 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 },
2660 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2665 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2667 path dir = "build/test/verify_invalid_extension_metadata1";
2668 auto dcp = make_simple (dir);
2669 dcp->set_annotation_text("A Test DCP");
2672 auto cpl = dcp->cpls()[0];
2675 Editor e (cpl->file().get());
2676 e.replace ("Application", "Fred");
2679 check_verify_result (
2682 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2683 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2688 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2690 path dir = "build/test/verify_invalid_extension_metadata2";
2691 auto dcp = make_simple (dir);
2692 dcp->set_annotation_text("A Test DCP");
2695 auto cpl = dcp->cpls()[0];
2698 Editor e (cpl->file().get());
2699 e.replace ("DCP Constraints Profile", "Fred");
2702 check_verify_result (
2705 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2711 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2713 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2714 auto dcp = make_simple (dir);
2715 dcp->set_annotation_text("A Test DCP");
2718 auto const cpl = dcp->cpls()[0];
2721 Editor e (cpl->file().get());
2722 e.replace ("<meta:Value>", "<meta:ValueX>");
2723 e.replace ("</meta:Value>", "</meta:ValueX>");
2726 check_verify_result (
2729 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2730 { 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 },
2731 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2736 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2738 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2739 auto dcp = make_simple (dir);
2740 dcp->set_annotation_text("A Test DCP");
2743 auto const cpl = dcp->cpls()[0];
2746 Editor e (cpl->file().get());
2747 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2750 check_verify_result (
2753 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2754 { 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() },
2759 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2761 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2762 auto dcp = make_simple (dir);
2763 dcp->set_annotation_text("A Test DCP");
2766 auto const cpl = dcp->cpls()[0];
2769 Editor e (cpl->file().get());
2770 e.replace ("<meta:Property>", "<meta:PropertyX>");
2771 e.replace ("</meta:Property>", "</meta:PropertyX>");
2774 check_verify_result (
2777 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2778 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2779 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2784 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2786 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2787 auto dcp = make_simple (dir);
2788 dcp->set_annotation_text("A Test DCP");
2791 auto const cpl = dcp->cpls()[0];
2794 Editor e (cpl->file().get());
2795 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2796 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2799 check_verify_result (
2802 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2803 { 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 },
2804 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2810 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2812 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2813 prepare_directory (dir);
2814 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2815 copy_file (i.path(), dir / i.path().filename());
2818 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2819 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2823 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2826 check_verify_result (
2829 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2831 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2832 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2833 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2834 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2835 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2836 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2841 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2843 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2844 prepare_directory (dir);
2845 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2846 copy_file (i.path(), dir / i.path().filename());
2849 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2850 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2853 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2856 check_verify_result (
2859 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2860 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2861 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2862 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2870 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2872 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2873 prepare_directory (dir);
2874 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2875 copy_file (i.path(), dir / i.path().filename());
2879 Editor e (dir / dcp_test1_pkl);
2880 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2883 check_verify_result ({dir}, {});
2887 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2889 path dir ("build/test/verify_must_not_be_partially_encrypted");
2890 prepare_directory (dir);
2894 auto signer = make_shared<dcp::CertificateChain>();
2895 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2896 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2897 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2898 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2900 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2904 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2907 auto writer = mp->start_write (dir / "video.mxf", false);
2908 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2909 for (int i = 0; i < 24; ++i) {
2910 writer->write (j2c.data(), j2c.size());
2912 writer->finalize ();
2914 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2916 auto reel = make_shared<dcp::Reel>(
2917 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2918 make_shared<dcp::ReelSoundAsset>(ms, 0)
2921 reel->add (simple_markers());
2925 cpl->set_content_version (
2926 {"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"}
2928 cpl->set_annotation_text ("A Test DCP");
2929 cpl->set_issuer ("OpenDCP 0.0.25");
2930 cpl->set_creator ("OpenDCP 0.0.25");
2931 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2932 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2933 cpl->set_main_sound_sample_rate (48000);
2934 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2935 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2936 cpl->set_version_number (1);
2940 d.set_issuer("OpenDCP 0.0.25");
2941 d.set_creator("OpenDCP 0.0.25");
2942 d.set_issue_date("2012-07-17T04:45:18+00:00");
2943 d.set_annotation_text("A Test DCP");
2944 d.write_xml(signer);
2946 check_verify_result (
2949 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2954 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2956 vector<dcp::VerificationNote> notes;
2957 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"));
2958 auto reader = picture.start_read ();
2959 auto frame = reader->get_frame (0);
2960 verify_j2k (frame, notes);
2961 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2965 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2967 vector<dcp::VerificationNote> notes;
2968 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2969 auto reader = picture.start_read ();
2970 auto frame = reader->get_frame (0);
2971 verify_j2k (frame, notes);
2972 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2976 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2978 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2979 prepare_directory (dir);
2980 auto dcp = make_simple (dir);
2982 vector<dcp::VerificationNote> notes;
2983 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2984 auto reader = picture.start_read ();
2985 auto frame = reader->get_frame (0);
2986 verify_j2k (frame, notes);
2987 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2991 /** Check that ResourceID and the XML ID being different is spotted */
2992 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2994 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2995 prepare_directory (dir);
2997 ASDCP::WriterInfo writer_info;
2998 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3001 auto mxf_id = dcp::make_uuid ();
3002 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3003 BOOST_REQUIRE (c == Kumu::UUID_Length);
3005 auto resource_id = dcp::make_uuid ();
3006 ASDCP::TimedText::TimedTextDescriptor descriptor;
3007 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3008 DCP_ASSERT (c == Kumu::UUID_Length);
3010 auto xml_id = dcp::make_uuid ();
3011 ASDCP::TimedText::MXFWriter writer;
3012 auto subs_mxf = dir / "subs.mxf";
3013 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3014 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3015 writer.WriteTimedTextResource (dcp::String::compose(
3016 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3017 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3018 "<Id>urn:uuid:%1</Id>"
3019 "<ContentTitleText>Content</ContentTitleText>"
3020 "<AnnotationText>Annotation</AnnotationText>"
3021 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3022 "<ReelNumber>1</ReelNumber>"
3023 "<Language>en-US</Language>"
3024 "<EditRate>25 1</EditRate>"
3025 "<TimeCodeRate>25</TimeCodeRate>"
3026 "<StartTime>00:00:00:00</StartTime>"
3028 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3029 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3030 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3039 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3040 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3042 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3044 check_verify_result (
3047 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3048 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3049 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3050 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3055 /** Check that ResourceID and the MXF ID being the same is spotted */
3056 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3058 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3059 prepare_directory (dir);
3061 ASDCP::WriterInfo writer_info;
3062 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3065 auto mxf_id = dcp::make_uuid ();
3066 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3067 BOOST_REQUIRE (c == Kumu::UUID_Length);
3069 auto resource_id = mxf_id;
3070 ASDCP::TimedText::TimedTextDescriptor descriptor;
3071 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3072 DCP_ASSERT (c == Kumu::UUID_Length);
3074 auto xml_id = resource_id;
3075 ASDCP::TimedText::MXFWriter writer;
3076 auto subs_mxf = dir / "subs.mxf";
3077 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3078 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3079 writer.WriteTimedTextResource (dcp::String::compose(
3080 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3081 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3082 "<Id>urn:uuid:%1</Id>"
3083 "<ContentTitleText>Content</ContentTitleText>"
3084 "<AnnotationText>Annotation</AnnotationText>"
3085 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3086 "<ReelNumber>1</ReelNumber>"
3087 "<Language>en-US</Language>"
3088 "<EditRate>25 1</EditRate>"
3089 "<TimeCodeRate>25</TimeCodeRate>"
3090 "<StartTime>00:00:00:00</StartTime>"
3092 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3093 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3094 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3103 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3104 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3106 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3108 check_verify_result (
3111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3112 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3113 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3114 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3119 /** Check a DCP with a 3D asset marked as 2D */
3120 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3122 check_verify_result (
3123 { private_test / "data" / "xm" },
3126 dcp::VerificationNote::Type::WARNING,
3127 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3130 dcp::VerificationNote::Type::BV21_ERROR,
3131 dcp::VerificationNote::Code::INVALID_STANDARD
3138 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3140 path dir = "build/test/verify_unexpected_things_in_main_markers";
3141 prepare_directory (dir);
3142 auto dcp = make_simple (dir, 1, 24);
3143 dcp->set_annotation_text("A Test DCP");
3147 Editor e (find_cpl(dir));
3149 " <IntrinsicDuration>24</IntrinsicDuration>",
3150 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3154 dcp::CPL cpl (find_cpl(dir));
3156 check_verify_result (
3159 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3160 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3161 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3166 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3168 path dir = "build/test/verify_invalid_content_kind";
3169 prepare_directory (dir);
3170 auto dcp = make_simple (dir, 1, 24);
3171 dcp->set_annotation_text("A Test DCP");
3175 Editor e(find_cpl(dir));
3176 e.replace("trailer", "trip");
3179 dcp::CPL cpl (find_cpl(dir));
3181 check_verify_result (
3184 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3185 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3191 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3193 path dir = "build/test/verify_valid_content_kind";
3194 prepare_directory (dir);
3195 auto dcp = make_simple (dir, 1, 24);
3196 dcp->set_annotation_text("A Test DCP");
3200 Editor e(find_cpl(dir));
3201 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3204 dcp::CPL cpl (find_cpl(dir));
3206 check_verify_result (
3209 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },