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 (
422 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
431 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
439 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
445 asset_map (string suffix)
447 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
451 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
453 check_verify_result_after_replace (
454 "invalid_picture_frame_rate", &cpl,
455 "<FrameRate>24 1", "<FrameRate>99 1",
456 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
457 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
461 BOOST_AUTO_TEST_CASE (verify_missing_asset)
463 auto dir = setup (1, "missing_asset");
464 remove (dir / "video.mxf");
465 check_verify_result (
468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
473 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
475 check_verify_result_after_replace (
476 "empty_asset_path", &asset_map,
477 "<Path>video.mxf</Path>", "<Path></Path>",
478 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
483 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
485 check_verify_result_after_replace (
486 "mismatched_standard", &cpl,
487 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
488 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
489 dcp::VerificationNote::Code::INVALID_XML,
490 dcp::VerificationNote::Code::INVALID_XML,
491 dcp::VerificationNote::Code::INVALID_XML,
492 dcp::VerificationNote::Code::INVALID_XML,
493 dcp::VerificationNote::Code::INVALID_XML,
494 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
499 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
501 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
502 check_verify_result_after_replace (
503 "invalid_xml_cpl_id", &cpl,
504 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
505 { dcp::VerificationNote::Code::INVALID_XML }
510 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
512 check_verify_result_after_replace (
513 "invalid_xml_issue_date", &cpl,
514 "<IssueDate>", "<IssueDate>x",
515 { dcp::VerificationNote::Code::INVALID_XML,
516 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
521 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
523 check_verify_result_after_replace (
524 "invalid_xml_pkl_id", &pkl,
525 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
526 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
527 { dcp::VerificationNote::Code::INVALID_XML }
532 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
534 check_verify_result_after_replace (
535 "invalid_xml_asset_map_id", &asset_map,
536 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
537 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
538 { dcp::VerificationNote::Code::INVALID_XML }
543 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
546 auto dir = setup (3, "verify_invalid_standard");
547 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
549 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
550 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
551 path const assetmap_file = dir / "ASSETMAP";
553 auto st = stages.begin();
554 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
555 BOOST_REQUIRE (st->second);
556 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
558 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
559 BOOST_REQUIRE (st->second);
560 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
562 BOOST_CHECK_EQUAL (st->first, "Checking reel");
563 BOOST_REQUIRE (!st->second);
565 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
566 BOOST_REQUIRE (st->second);
567 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
569 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
570 BOOST_REQUIRE (st->second);
571 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
573 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
574 BOOST_REQUIRE (st->second);
575 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
577 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
578 BOOST_REQUIRE (st->second);
579 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
581 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
582 BOOST_REQUIRE (st->second);
583 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
585 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
586 BOOST_REQUIRE (st->second);
587 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
589 BOOST_REQUIRE (st == stages.end());
591 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
592 auto i = notes.begin ();
593 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
594 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
596 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
597 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
600 /* DCP with a short asset */
601 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
603 auto dir = setup (8, "invalid_duration");
604 check_verify_result (
607 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
608 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
609 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
610 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
611 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
612 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
619 dcp_from_frame (dcp::ArrayData const& frame, path dir)
621 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
622 create_directories (dir);
623 auto writer = asset->start_write (dir / "pic.mxf", true);
624 for (int i = 0; i < 24; ++i) {
625 writer->write (frame.data(), frame.size());
629 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
630 return write_dcp_with_single_asset (dir, reel_asset);
634 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
636 int const too_big = 1302083 * 2;
638 /* Compress a black image */
639 auto image = black_image ();
640 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
641 BOOST_REQUIRE (frame.size() < too_big);
643 /* Place it in a bigger block with some zero padding at the end */
644 dcp::ArrayData oversized_frame(too_big);
645 memcpy (oversized_frame.data(), frame.data(), frame.size());
646 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
648 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
649 prepare_directory (dir);
650 auto cpl = dcp_from_frame (oversized_frame, dir);
652 check_verify_result (
655 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
656 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
657 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
662 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
664 int const nearly_too_big = 1302083 * 0.98;
666 /* Compress a black image */
667 auto image = black_image ();
668 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
669 BOOST_REQUIRE (frame.size() < nearly_too_big);
671 /* Place it in a bigger block with some zero padding at the end */
672 dcp::ArrayData oversized_frame(nearly_too_big);
673 memcpy (oversized_frame.data(), frame.data(), frame.size());
674 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
676 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
677 prepare_directory (dir);
678 auto cpl = dcp_from_frame (oversized_frame, dir);
680 check_verify_result (
683 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
684 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
690 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
692 /* Compress a black image */
693 auto image = black_image ();
694 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
695 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
697 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
698 prepare_directory (dir);
699 auto cpl = dcp_from_frame (frame, dir);
701 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
705 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
707 path const dir("build/test/verify_valid_interop_subtitles");
708 prepare_directory (dir);
709 copy_file ("test/data/subs1.xml", dir / "subs.xml");
710 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
711 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
712 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
714 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
718 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
720 using namespace boost::filesystem;
722 path const dir("build/test/verify_invalid_interop_subtitles");
723 prepare_directory (dir);
724 copy_file ("test/data/subs1.xml", dir / "subs.xml");
725 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
726 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
727 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
730 Editor e (dir / "subs.xml");
731 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
734 check_verify_result (
737 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
738 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
740 dcp::VerificationNote::Type::ERROR,
741 dcp::VerificationNote::Code::INVALID_XML,
742 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
750 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
752 path const dir("build/test/verify_valid_smpte_subtitles");
753 prepare_directory (dir);
754 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
755 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
756 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
757 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
759 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
763 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
765 using namespace boost::filesystem;
767 path const dir("build/test/verify_invalid_smpte_subtitles");
768 prepare_directory (dir);
769 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
770 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
771 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
772 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
773 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
775 check_verify_result (
778 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
780 dcp::VerificationNote::Type::ERROR,
781 dcp::VerificationNote::Code::INVALID_XML,
782 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
787 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
792 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
794 path const dir("build/test/verify_empty_text_node_in_subtitles");
795 prepare_directory (dir);
796 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
797 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
798 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
799 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
801 check_verify_result (
804 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
805 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
806 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
807 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
812 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
813 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
815 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
816 prepare_directory (dir);
817 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
818 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
819 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
820 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
822 check_verify_result (
825 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
830 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
831 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
833 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
834 prepare_directory (dir);
835 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
836 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
837 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
838 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
840 check_verify_result (
843 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
844 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
849 BOOST_AUTO_TEST_CASE (verify_external_asset)
851 path const ov_dir("build/test/verify_external_asset");
852 prepare_directory (ov_dir);
854 auto image = black_image ();
855 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
856 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
857 dcp_from_frame (frame, ov_dir);
859 dcp::DCP ov (ov_dir);
862 path const vf_dir("build/test/verify_external_asset_vf");
863 prepare_directory (vf_dir);
865 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
866 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
868 check_verify_result (
871 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
872 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
877 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
879 path const dir("build/test/verify_valid_cpl_metadata");
880 prepare_directory (dir);
882 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
883 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
884 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
886 auto reel = make_shared<dcp::Reel>();
887 reel->add (reel_asset);
889 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
890 reel->add (simple_markers(16 * 24));
892 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
894 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
895 cpl->set_main_sound_sample_rate (48000);
896 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
897 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
898 cpl->set_version_number (1);
902 dcp.set_annotation_text("hello");
907 path find_cpl (path dir)
909 for (auto i: directory_iterator(dir)) {
910 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
915 BOOST_REQUIRE (false);
920 /* DCP with invalid CompositionMetadataAsset */
921 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
923 using namespace boost::filesystem;
925 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
926 prepare_directory (dir);
928 auto reel = make_shared<dcp::Reel>();
929 reel->add (black_picture_asset(dir));
930 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
932 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
933 cpl->set_main_sound_sample_rate (48000);
934 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
935 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
936 cpl->set_version_number (1);
938 reel->add (simple_markers());
942 dcp.set_annotation_text("hello");
946 Editor e (find_cpl(dir));
947 e.replace ("MainSound", "MainSoundX");
950 check_verify_result (
953 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
954 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
956 dcp::VerificationNote::Type::ERROR,
957 dcp::VerificationNote::Code::INVALID_XML,
958 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
959 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
960 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
961 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
962 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
963 "ExtensionMetadataList?,)'"),
964 canonical(cpl->file().get()),
967 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
972 /* DCP with invalid CompositionMetadataAsset */
973 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
975 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
976 prepare_directory (dir);
978 auto reel = make_shared<dcp::Reel>();
979 reel->add (black_picture_asset(dir));
980 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
982 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
983 cpl->set_main_sound_sample_rate (48000);
984 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
985 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
989 dcp.set_annotation_text("hello");
993 Editor e (find_cpl(dir));
994 e.replace ("meta:Width", "meta:WidthX");
997 check_verify_result (
999 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1004 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1006 path const dir("build/test/verify_invalid_language1");
1007 prepare_directory (dir);
1008 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1009 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1010 asset->_language = "wrong-andbad";
1011 asset->write (dir / "subs.mxf");
1012 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1013 reel_asset->_language = "badlang";
1014 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1016 check_verify_result (
1019 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1020 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1021 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1026 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1027 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1029 path const dir("build/test/verify_invalid_language2");
1030 prepare_directory (dir);
1031 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1032 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1033 asset->_language = "wrong-andbad";
1034 asset->write (dir / "subs.mxf");
1035 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1036 reel_asset->_language = "badlang";
1037 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1039 check_verify_result (
1042 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1043 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1044 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1049 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1050 * the release territory.
1052 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1054 path const dir("build/test/verify_invalid_language3");
1055 prepare_directory (dir);
1057 auto picture = simple_picture (dir, "foo");
1058 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1059 auto reel = make_shared<dcp::Reel>();
1060 reel->add (reel_picture);
1061 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1062 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1063 reel->add (reel_sound);
1064 reel->add (simple_markers());
1066 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1068 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1069 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1070 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1071 cpl->set_main_sound_sample_rate (48000);
1072 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1073 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1074 cpl->set_version_number (1);
1075 cpl->_release_territory = "fred-jim";
1076 auto dcp = make_shared<dcp::DCP>(dir);
1078 dcp->set_annotation_text("hello");
1081 check_verify_result (
1084 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1085 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1093 vector<dcp::VerificationNote>
1094 check_picture_size (int width, int height, int frame_rate, bool three_d)
1096 using namespace boost::filesystem;
1098 path dcp_path = "build/test/verify_picture_test";
1099 prepare_directory (dcp_path);
1101 shared_ptr<dcp::PictureAsset> mp;
1103 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1105 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1107 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1109 auto image = black_image (dcp::Size(width, height));
1110 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1111 int const length = three_d ? frame_rate * 2 : frame_rate;
1112 for (int i = 0; i < length; ++i) {
1113 picture_writer->write (j2c.data(), j2c.size());
1115 picture_writer->finalize ();
1117 auto d = make_shared<dcp::DCP>(dcp_path);
1118 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1119 cpl->set_annotation_text ("A Test DCP");
1120 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1121 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1122 cpl->set_main_sound_sample_rate (48000);
1123 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1124 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1125 cpl->set_version_number (1);
1127 auto reel = make_shared<dcp::Reel>();
1130 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1132 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1135 reel->add (simple_markers(frame_rate));
1140 d->set_annotation_text("A Test DCP");
1143 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1149 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1151 auto notes = check_picture_size(width, height, frame_rate, three_d);
1152 BOOST_CHECK_EQUAL (notes.size(), 0U);
1158 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1160 auto notes = check_picture_size(width, height, frame_rate, three_d);
1161 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1162 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1163 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1169 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1171 auto notes = check_picture_size(width, height, frame_rate, three_d);
1172 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1173 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1174 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1180 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1182 auto notes = check_picture_size(width, height, frame_rate, three_d);
1183 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1184 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1185 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1189 BOOST_AUTO_TEST_CASE (verify_picture_size)
1191 using namespace boost::filesystem;
1194 check_picture_size_ok (2048, 858, 24, false);
1195 check_picture_size_ok (2048, 858, 25, false);
1196 check_picture_size_ok (2048, 858, 48, false);
1197 check_picture_size_ok (2048, 858, 24, true);
1198 check_picture_size_ok (2048, 858, 25, true);
1199 check_picture_size_ok (2048, 858, 48, true);
1202 check_picture_size_ok (1998, 1080, 24, false);
1203 check_picture_size_ok (1998, 1080, 25, false);
1204 check_picture_size_ok (1998, 1080, 48, false);
1205 check_picture_size_ok (1998, 1080, 24, true);
1206 check_picture_size_ok (1998, 1080, 25, true);
1207 check_picture_size_ok (1998, 1080, 48, true);
1210 check_picture_size_ok (4096, 1716, 24, false);
1213 check_picture_size_ok (3996, 2160, 24, false);
1215 /* Bad frame size */
1216 check_picture_size_bad_frame_size (2050, 858, 24, false);
1217 check_picture_size_bad_frame_size (2048, 658, 25, false);
1218 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1219 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1221 /* Bad 2K frame rate */
1222 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1223 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1224 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1226 /* Bad 4K frame rate */
1227 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1228 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1231 auto notes = check_picture_size(3996, 2160, 24, true);
1232 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1233 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1234 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1240 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")
1243 make_shared<dcp::SubtitleString>(
1251 dcp::Time(start_frame, 24, 24),
1252 dcp::Time(end_frame, 24, 24),
1254 dcp::HAlign::CENTER,
1257 dcp::Direction::LTR,
1269 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1271 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1272 prepare_directory (dir);
1274 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1275 for (int i = 0; i < 2048; ++i) {
1276 add_test_subtitle (asset, i * 24, i * 24 + 20);
1278 asset->set_language (dcp::LanguageTag("de-DE"));
1279 asset->write (dir / "subs.mxf");
1280 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1281 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1283 check_verify_result (
1286 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1288 dcp::VerificationNote::Type::BV21_ERROR,
1289 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1291 canonical(dir / "subs.mxf")
1293 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1294 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1300 shared_ptr<dcp::SMPTESubtitleAsset>
1301 make_large_subtitle_asset (path font_file)
1303 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1304 dcp::ArrayData big_fake_font(1024 * 1024);
1305 big_fake_font.write (font_file);
1306 for (int i = 0; i < 116; ++i) {
1307 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1315 verify_timed_text_asset_too_large (string name)
1317 auto const dir = path("build/test") / name;
1318 prepare_directory (dir);
1319 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1320 add_test_subtitle (asset, 0, 240);
1321 asset->set_language (dcp::LanguageTag("de-DE"));
1322 asset->write (dir / "subs.mxf");
1324 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1325 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1327 check_verify_result (
1330 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1331 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1333 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1339 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1341 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1342 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1346 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1348 path dir = "build/test/verify_missing_subtitle_language";
1349 prepare_directory (dir);
1350 auto dcp = make_simple (dir, 1, 106);
1353 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1354 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1355 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1356 "<ContentTitleText>Content</ContentTitleText>"
1357 "<AnnotationText>Annotation</AnnotationText>"
1358 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1359 "<ReelNumber>1</ReelNumber>"
1360 "<EditRate>24 1</EditRate>"
1361 "<TimeCodeRate>24</TimeCodeRate>"
1362 "<StartTime>00:00:00:00</StartTime>"
1363 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1365 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1366 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1367 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1373 dcp::File xml_file(dir / "subs.xml", "w");
1374 BOOST_REQUIRE (xml_file);
1375 xml_file.write(xml.c_str(), xml.size(), 1);
1377 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1378 subs->write (dir / "subs.mxf");
1380 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1381 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1382 dcp->set_annotation_text("A Test DCP");
1385 check_verify_result (
1388 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1389 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1394 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1396 path path ("build/test/verify_mismatched_subtitle_languages");
1397 auto constexpr reel_length = 192;
1398 auto dcp = make_simple (path, 2, reel_length);
1399 auto cpl = dcp->cpls()[0];
1402 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1403 subs->set_language (dcp::LanguageTag("de-DE"));
1404 subs->add (simple_subtitle());
1405 subs->write (path / "subs1.mxf");
1406 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1407 cpl->reels()[0]->add(reel_subs);
1411 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1412 subs->set_language (dcp::LanguageTag("en-US"));
1413 subs->add (simple_subtitle());
1414 subs->write (path / "subs2.mxf");
1415 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1416 cpl->reels()[1]->add(reel_subs);
1419 dcp->set_annotation_text("A Test DCP");
1422 check_verify_result (
1425 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1426 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1427 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1432 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1434 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1435 auto constexpr reel_length = 192;
1436 auto dcp = make_simple (path, 2, reel_length);
1437 auto cpl = dcp->cpls()[0];
1440 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1441 ccaps->set_language (dcp::LanguageTag("de-DE"));
1442 ccaps->add (simple_subtitle());
1443 ccaps->write (path / "subs1.mxf");
1444 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1445 cpl->reels()[0]->add(reel_ccaps);
1449 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1450 ccaps->set_language (dcp::LanguageTag("en-US"));
1451 ccaps->add (simple_subtitle());
1452 ccaps->write (path / "subs2.mxf");
1453 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1454 cpl->reels()[1]->add(reel_ccaps);
1457 dcp->set_annotation_text("A Test DCP");
1460 check_verify_result (
1463 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1464 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1469 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1471 path dir = "build/test/verify_missing_subtitle_start_time";
1472 prepare_directory (dir);
1473 auto dcp = make_simple (dir, 1, 106);
1476 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1477 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1478 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1479 "<ContentTitleText>Content</ContentTitleText>"
1480 "<AnnotationText>Annotation</AnnotationText>"
1481 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1482 "<ReelNumber>1</ReelNumber>"
1483 "<Language>de-DE</Language>"
1484 "<EditRate>24 1</EditRate>"
1485 "<TimeCodeRate>24</TimeCodeRate>"
1486 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1488 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1489 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1490 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1496 dcp::File xml_file(dir / "subs.xml", "w");
1497 BOOST_REQUIRE (xml_file);
1498 xml_file.write(xml.c_str(), xml.size(), 1);
1500 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1501 subs->write (dir / "subs.mxf");
1503 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1504 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1505 dcp->set_annotation_text("A Test DCP");
1508 check_verify_result (
1511 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1512 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1517 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1519 path dir = "build/test/verify_invalid_subtitle_start_time";
1520 prepare_directory (dir);
1521 auto dcp = make_simple (dir, 1, 106);
1524 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1525 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1526 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1527 "<ContentTitleText>Content</ContentTitleText>"
1528 "<AnnotationText>Annotation</AnnotationText>"
1529 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1530 "<ReelNumber>1</ReelNumber>"
1531 "<Language>de-DE</Language>"
1532 "<EditRate>24 1</EditRate>"
1533 "<TimeCodeRate>24</TimeCodeRate>"
1534 "<StartTime>00:00:02:00</StartTime>"
1535 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1537 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1538 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1539 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1545 dcp::File xml_file(dir / "subs.xml", "w");
1546 BOOST_REQUIRE (xml_file);
1547 xml_file.write(xml.c_str(), xml.size(), 1);
1549 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1550 subs->write (dir / "subs.mxf");
1552 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1553 dcp->cpls().front()->reels().front()->add(reel_subs);
1554 dcp->set_annotation_text("A Test DCP");
1557 check_verify_result (
1560 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1561 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1569 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1572 , v_position(v_position_)
1580 dcp::VAlign v_align;
1586 shared_ptr<dcp::CPL>
1587 dcp_with_text (path dir, vector<TestText> subs)
1589 prepare_directory (dir);
1590 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1591 asset->set_start_time (dcp::Time());
1592 for (auto i: subs) {
1593 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1595 asset->set_language (dcp::LanguageTag("de-DE"));
1596 asset->write (dir / "subs.mxf");
1598 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1599 return write_dcp_with_single_asset (dir, reel_asset);
1604 shared_ptr<dcp::CPL>
1605 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1607 prepare_directory (dir);
1608 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1609 asset->set_start_time (dcp::Time());
1610 asset->set_language (dcp::LanguageTag("de-DE"));
1612 auto subs_mxf = dir / "subs.mxf";
1613 asset->write (subs_mxf);
1615 /* The call to write() puts the asset into the DCP correctly but it will have
1616 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1619 ASDCP::TimedText::MXFWriter writer;
1620 ASDCP::WriterInfo writer_info;
1621 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1623 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1624 DCP_ASSERT (c == Kumu::UUID_Length);
1625 ASDCP::TimedText::TimedTextDescriptor descriptor;
1626 descriptor.ContainerDuration = asset->intrinsic_duration();
1627 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1628 DCP_ASSERT (c == Kumu::UUID_Length);
1629 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1630 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1631 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1632 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1635 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1636 return write_dcp_with_single_asset (dir, reel_asset);
1640 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1642 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1643 /* Just too early */
1644 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1645 check_verify_result (
1648 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1649 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1655 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1657 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1658 /* Just late enough */
1659 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1660 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1664 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1666 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1667 prepare_directory (dir);
1669 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1670 asset1->set_start_time (dcp::Time());
1671 /* Just late enough */
1672 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1673 asset1->set_language (dcp::LanguageTag("de-DE"));
1674 asset1->write (dir / "subs1.mxf");
1675 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1676 auto reel1 = make_shared<dcp::Reel>();
1677 reel1->add (reel_asset1);
1678 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1679 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1680 reel1->add (markers1);
1682 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1683 asset2->set_start_time (dcp::Time());
1684 /* This would be too early on first reel but should be OK on the second */
1685 add_test_subtitle (asset2, 3, 4 * 24);
1686 asset2->set_language (dcp::LanguageTag("de-DE"));
1687 asset2->write (dir / "subs2.mxf");
1688 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1689 auto reel2 = make_shared<dcp::Reel>();
1690 reel2->add (reel_asset2);
1691 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1692 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1693 reel2->add (markers2);
1695 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1698 auto dcp = make_shared<dcp::DCP>(dir);
1700 dcp->set_annotation_text("hello");
1703 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1707 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1709 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1710 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1714 { 5 * 24 + 1, 6 * 24 },
1716 check_verify_result (
1719 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1720 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1725 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1727 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1728 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1732 { 5 * 24 + 16, 8 * 24 },
1734 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1738 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1740 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1741 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1742 check_verify_result (
1745 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1746 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1751 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1753 auto const dir = path("build/test/verify_valid_subtitle_duration");
1754 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1755 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1759 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1761 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1762 prepare_directory (dir);
1763 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1764 asset->set_start_time (dcp::Time());
1765 add_test_subtitle (asset, 0, 4 * 24);
1766 asset->set_language (dcp::LanguageTag("de-DE"));
1767 asset->write (dir / "subs.mxf");
1769 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1770 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1771 check_verify_result (
1774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1775 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1776 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1783 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1785 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1786 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1789 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1790 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1791 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1792 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1794 check_verify_result (
1797 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1803 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1805 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1806 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1809 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1810 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1811 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1813 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1817 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1819 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1820 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1823 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1824 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1825 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1826 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1828 check_verify_result (
1831 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1832 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1837 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1839 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1840 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1843 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1844 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1845 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1846 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1848 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1852 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1854 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1855 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1858 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1860 check_verify_result (
1863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1869 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1871 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1872 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1875 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1877 check_verify_result (
1880 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1881 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1886 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1888 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1889 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1892 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1893 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1894 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1895 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1897 check_verify_result (
1900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1906 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1908 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1909 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1912 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1913 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1914 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1916 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1920 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1922 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1923 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1926 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1927 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1928 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1929 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1931 check_verify_result (
1934 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1940 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1942 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1943 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1946 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1947 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1948 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1949 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1951 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1955 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1957 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1958 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1961 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1963 check_verify_result (
1966 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1971 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1973 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1974 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1977 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
1979 check_verify_result (
1982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1988 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
1990 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
1991 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1994 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
1995 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
1996 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
1998 check_verify_result (
2001 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2006 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2008 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2009 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2012 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2013 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2014 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2016 check_verify_result (
2019 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2020 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2025 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2027 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2028 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2031 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2032 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2033 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2035 check_verify_result (
2038 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2043 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2045 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2046 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2049 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2050 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2051 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2053 check_verify_result (
2056 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2061 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2063 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2064 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2074 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2076 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2077 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2078 check_verify_result (
2081 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2087 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2089 path const dir("build/test/verify_invalid_sound_frame_rate");
2090 prepare_directory (dir);
2092 auto picture = simple_picture (dir, "foo");
2093 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2094 auto reel = make_shared<dcp::Reel>();
2095 reel->add (reel_picture);
2096 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2097 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2098 reel->add (reel_sound);
2099 reel->add (simple_markers());
2100 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2102 auto dcp = make_shared<dcp::DCP>(dir);
2104 dcp->set_annotation_text("hello");
2107 check_verify_result (
2110 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2116 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2118 path const dir("build/test/verify_missing_cpl_annotation_text");
2119 auto dcp = make_simple (dir);
2120 dcp->set_annotation_text("A Test DCP");
2123 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2125 auto const cpl = dcp->cpls()[0];
2128 BOOST_REQUIRE (cpl->file());
2129 Editor e(cpl->file().get());
2130 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2133 check_verify_result (
2136 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2137 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2142 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2144 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2145 auto dcp = make_simple (dir);
2146 dcp->set_annotation_text("A Test DCP");
2149 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2150 auto const cpl = dcp->cpls()[0];
2153 BOOST_REQUIRE (cpl->file());
2154 Editor e(cpl->file().get());
2155 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2158 check_verify_result (
2161 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2162 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2167 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2169 path const dir("build/test/verify_mismatched_asset_duration");
2170 prepare_directory (dir);
2171 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2172 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2174 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2175 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2177 auto reel = make_shared<dcp::Reel>(
2178 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2179 make_shared<dcp::ReelSoundAsset>(ms, 0)
2182 reel->add (simple_markers());
2186 dcp->set_annotation_text("A Test DCP");
2189 check_verify_result (
2192 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2200 shared_ptr<dcp::CPL>
2201 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2203 prepare_directory (dir);
2204 auto dcp = make_shared<dcp::DCP>(dir);
2205 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2207 auto constexpr reel_length = 192;
2209 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2210 subs->set_language (dcp::LanguageTag("de-DE"));
2211 subs->set_start_time (dcp::Time());
2212 subs->add (simple_subtitle());
2213 subs->write (dir / "subs.mxf");
2214 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2216 auto reel1 = make_shared<dcp::Reel>(
2217 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2218 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2222 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2225 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2226 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2227 reel1->add (markers1);
2231 auto reel2 = make_shared<dcp::Reel>(
2232 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2233 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2237 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2240 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2241 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2242 reel2->add (markers2);
2247 dcp->set_annotation_text("A Test DCP");
2254 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2257 path dir ("build/test/missing_main_subtitle_from_some_reels");
2258 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2259 check_verify_result (
2262 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2263 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2269 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2270 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2271 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2275 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2276 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2277 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2283 shared_ptr<dcp::CPL>
2284 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2286 prepare_directory (dir);
2287 auto dcp = make_shared<dcp::DCP>(dir);
2288 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2290 auto constexpr reel_length = 192;
2292 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2293 subs->set_language (dcp::LanguageTag("de-DE"));
2294 subs->set_start_time (dcp::Time());
2295 subs->add (simple_subtitle());
2296 subs->write (dir / "subs.mxf");
2298 auto reel1 = make_shared<dcp::Reel>(
2299 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2300 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2303 for (int i = 0; i < caps_in_reel1; ++i) {
2304 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2307 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2308 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2309 reel1->add (markers1);
2313 auto reel2 = make_shared<dcp::Reel>(
2314 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2315 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2318 for (int i = 0; i < caps_in_reel2; ++i) {
2319 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2322 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2323 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2324 reel2->add (markers2);
2329 dcp->set_annotation_text("A Test DCP");
2336 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2339 path dir ("build/test/mismatched_closed_caption_asset_counts");
2340 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2341 check_verify_result (
2344 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2345 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2350 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2351 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2352 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2356 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2357 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2358 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2365 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2367 prepare_directory (dir);
2368 auto dcp = make_shared<dcp::DCP>(dir);
2369 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2371 auto constexpr reel_length = 192;
2373 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2374 subs->set_language (dcp::LanguageTag("de-DE"));
2375 subs->set_start_time (dcp::Time());
2376 subs->add (simple_subtitle());
2377 subs->write (dir / "subs.mxf");
2378 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2381 auto reel = make_shared<dcp::Reel>(
2382 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2383 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2386 reel->add (reel_text);
2388 reel->add (simple_markers(reel_length));
2393 dcp->set_annotation_text("A Test DCP");
2396 check_verify_result (
2399 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2400 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2405 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2407 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2408 "build/test/verify_subtitle_entry_point_must_be_present",
2409 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2410 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2411 asset->unset_entry_point ();
2415 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2416 "build/test/verify_subtitle_entry_point_must_be_zero",
2417 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2418 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2419 asset->set_entry_point (4);
2423 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2424 "build/test/verify_closed_caption_entry_point_must_be_present",
2425 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2426 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2427 asset->unset_entry_point ();
2431 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2432 "build/test/verify_closed_caption_entry_point_must_be_zero",
2433 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2434 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2435 asset->set_entry_point (9);
2441 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2445 path const dir("build/test/verify_missing_hash");
2446 auto dcp = make_simple (dir);
2447 dcp->set_annotation_text("A Test DCP");
2450 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2451 auto const cpl = dcp->cpls()[0];
2452 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2453 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2454 auto asset_id = cpl->reels()[0]->main_picture()->id();
2457 BOOST_REQUIRE (cpl->file());
2458 Editor e(cpl->file().get());
2459 e.delete_first_line_containing("<Hash>");
2462 check_verify_result (
2465 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2466 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2473 verify_markers_test (
2475 vector<pair<dcp::Marker, dcp::Time>> markers,
2476 vector<dcp::VerificationNote> test_notes
2479 auto dcp = make_simple (dir);
2480 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2481 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2482 for (auto const& i: markers) {
2483 markers_asset->set (i.first, i.second);
2485 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2486 dcp->set_annotation_text("A Test DCP");
2489 check_verify_result ({dir}, test_notes);
2493 BOOST_AUTO_TEST_CASE (verify_markers)
2495 verify_markers_test (
2496 "build/test/verify_markers_all_correct",
2498 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2499 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2500 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2501 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2506 verify_markers_test (
2507 "build/test/verify_markers_missing_ffec",
2509 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2510 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2511 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2514 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2517 verify_markers_test (
2518 "build/test/verify_markers_missing_ffmc",
2520 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2521 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2522 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2525 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2528 verify_markers_test (
2529 "build/test/verify_markers_missing_ffoc",
2531 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2532 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2533 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2536 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2539 verify_markers_test (
2540 "build/test/verify_markers_missing_lfoc",
2542 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2543 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2544 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2547 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2550 verify_markers_test (
2551 "build/test/verify_markers_incorrect_ffoc",
2553 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2554 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2555 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2556 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2559 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2562 verify_markers_test (
2563 "build/test/verify_markers_incorrect_lfoc",
2565 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2566 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2567 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2568 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2571 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2576 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2578 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2579 prepare_directory (dir);
2580 auto dcp = make_simple (dir);
2581 auto cpl = dcp->cpls()[0];
2582 cpl->unset_version_number();
2583 dcp->set_annotation_text("A Test DCP");
2586 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2590 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2592 path dir = "build/test/verify_missing_extension_metadata1";
2593 auto dcp = make_simple (dir);
2594 dcp->set_annotation_text("A Test DCP");
2597 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2598 auto cpl = dcp->cpls()[0];
2601 Editor e (cpl->file().get());
2602 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2605 check_verify_result (
2608 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2609 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2614 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2616 path dir = "build/test/verify_missing_extension_metadata2";
2617 auto dcp = make_simple (dir);
2618 dcp->set_annotation_text("A Test DCP");
2621 auto cpl = dcp->cpls()[0];
2624 Editor e (cpl->file().get());
2625 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2628 check_verify_result (
2631 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2632 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2637 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2639 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2640 auto dcp = make_simple (dir);
2641 dcp->set_annotation_text("A Test DCP");
2644 auto const cpl = dcp->cpls()[0];
2647 Editor e (cpl->file().get());
2648 e.replace ("<meta:Name>A", "<meta:NameX>A");
2649 e.replace ("n</meta:Name>", "n</meta:NameX>");
2652 check_verify_result (
2655 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2656 { 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 },
2657 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2662 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2664 path dir = "build/test/verify_invalid_extension_metadata1";
2665 auto dcp = make_simple (dir);
2666 dcp->set_annotation_text("A Test DCP");
2669 auto cpl = dcp->cpls()[0];
2672 Editor e (cpl->file().get());
2673 e.replace ("Application", "Fred");
2676 check_verify_result (
2679 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2680 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2685 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2687 path dir = "build/test/verify_invalid_extension_metadata2";
2688 auto dcp = make_simple (dir);
2689 dcp->set_annotation_text("A Test DCP");
2692 auto cpl = dcp->cpls()[0];
2695 Editor e (cpl->file().get());
2696 e.replace ("DCP Constraints Profile", "Fred");
2699 check_verify_result (
2702 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2708 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2710 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2711 auto dcp = make_simple (dir);
2712 dcp->set_annotation_text("A Test DCP");
2715 auto const cpl = dcp->cpls()[0];
2718 Editor e (cpl->file().get());
2719 e.replace ("<meta:Value>", "<meta:ValueX>");
2720 e.replace ("</meta:Value>", "</meta:ValueX>");
2723 check_verify_result (
2726 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2727 { 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 },
2728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2733 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2735 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2736 auto dcp = make_simple (dir);
2737 dcp->set_annotation_text("A Test DCP");
2740 auto const cpl = dcp->cpls()[0];
2743 Editor e (cpl->file().get());
2744 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2747 check_verify_result (
2750 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2751 { 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() },
2756 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2758 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2759 auto dcp = make_simple (dir);
2760 dcp->set_annotation_text("A Test DCP");
2763 auto const cpl = dcp->cpls()[0];
2766 Editor e (cpl->file().get());
2767 e.replace ("<meta:Property>", "<meta:PropertyX>");
2768 e.replace ("</meta:Property>", "</meta:PropertyX>");
2771 check_verify_result (
2774 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2775 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2776 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2781 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2783 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2784 auto dcp = make_simple (dir);
2785 dcp->set_annotation_text("A Test DCP");
2788 auto const cpl = dcp->cpls()[0];
2791 Editor e (cpl->file().get());
2792 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2793 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2796 check_verify_result (
2799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2800 { 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 },
2801 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2807 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2809 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2810 prepare_directory (dir);
2811 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2812 copy_file (i.path(), dir / i.path().filename());
2815 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2816 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2820 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2823 check_verify_result (
2826 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2828 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2829 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2830 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2831 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2832 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2833 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2838 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2840 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2841 prepare_directory (dir);
2842 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2843 copy_file (i.path(), dir / i.path().filename());
2846 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2847 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2850 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2853 check_verify_result (
2856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2859 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2860 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2861 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2862 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2867 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2869 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2870 prepare_directory (dir);
2871 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2872 copy_file (i.path(), dir / i.path().filename());
2876 Editor e (dir / dcp_test1_pkl);
2877 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2880 check_verify_result ({dir}, {});
2884 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2886 path dir ("build/test/verify_must_not_be_partially_encrypted");
2887 prepare_directory (dir);
2891 auto signer = make_shared<dcp::CertificateChain>();
2892 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2893 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2894 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2895 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2897 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2901 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2904 auto writer = mp->start_write (dir / "video.mxf", false);
2905 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2906 for (int i = 0; i < 24; ++i) {
2907 writer->write (j2c.data(), j2c.size());
2909 writer->finalize ();
2911 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2913 auto reel = make_shared<dcp::Reel>(
2914 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2915 make_shared<dcp::ReelSoundAsset>(ms, 0)
2918 reel->add (simple_markers());
2922 cpl->set_content_version (
2923 {"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"}
2925 cpl->set_annotation_text ("A Test DCP");
2926 cpl->set_issuer ("OpenDCP 0.0.25");
2927 cpl->set_creator ("OpenDCP 0.0.25");
2928 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2929 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2930 cpl->set_main_sound_sample_rate (48000);
2931 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2932 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2933 cpl->set_version_number (1);
2937 d.set_issuer("OpenDCP 0.0.25");
2938 d.set_creator("OpenDCP 0.0.25");
2939 d.set_issue_date("2012-07-17T04:45:18+00:00");
2940 d.set_annotation_text("A Test DCP");
2941 d.write_xml(signer);
2943 check_verify_result (
2946 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2951 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2953 vector<dcp::VerificationNote> notes;
2954 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"));
2955 auto reader = picture.start_read ();
2956 auto frame = reader->get_frame (0);
2957 verify_j2k (frame, notes);
2958 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2962 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2964 vector<dcp::VerificationNote> notes;
2965 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2966 auto reader = picture.start_read ();
2967 auto frame = reader->get_frame (0);
2968 verify_j2k (frame, notes);
2969 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2973 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2975 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2976 prepare_directory (dir);
2977 auto dcp = make_simple (dir);
2979 vector<dcp::VerificationNote> notes;
2980 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2981 auto reader = picture.start_read ();
2982 auto frame = reader->get_frame (0);
2983 verify_j2k (frame, notes);
2984 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2988 /** Check that ResourceID and the XML ID being different is spotted */
2989 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2991 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2992 prepare_directory (dir);
2994 ASDCP::WriterInfo writer_info;
2995 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2998 auto mxf_id = dcp::make_uuid ();
2999 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3000 BOOST_REQUIRE (c == Kumu::UUID_Length);
3002 auto resource_id = dcp::make_uuid ();
3003 ASDCP::TimedText::TimedTextDescriptor descriptor;
3004 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3005 DCP_ASSERT (c == Kumu::UUID_Length);
3007 auto xml_id = dcp::make_uuid ();
3008 ASDCP::TimedText::MXFWriter writer;
3009 auto subs_mxf = dir / "subs.mxf";
3010 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3011 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3012 writer.WriteTimedTextResource (dcp::String::compose(
3013 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3014 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3015 "<Id>urn:uuid:%1</Id>"
3016 "<ContentTitleText>Content</ContentTitleText>"
3017 "<AnnotationText>Annotation</AnnotationText>"
3018 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3019 "<ReelNumber>1</ReelNumber>"
3020 "<Language>en-US</Language>"
3021 "<EditRate>25 1</EditRate>"
3022 "<TimeCodeRate>25</TimeCodeRate>"
3023 "<StartTime>00:00:00:00</StartTime>"
3025 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3026 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3027 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3036 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3037 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3039 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3041 check_verify_result (
3044 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3046 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3047 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3052 /** Check that ResourceID and the MXF ID being the same is spotted */
3053 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3055 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3056 prepare_directory (dir);
3058 ASDCP::WriterInfo writer_info;
3059 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3062 auto mxf_id = dcp::make_uuid ();
3063 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3064 BOOST_REQUIRE (c == Kumu::UUID_Length);
3066 auto resource_id = mxf_id;
3067 ASDCP::TimedText::TimedTextDescriptor descriptor;
3068 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3069 DCP_ASSERT (c == Kumu::UUID_Length);
3071 auto xml_id = resource_id;
3072 ASDCP::TimedText::MXFWriter writer;
3073 auto subs_mxf = dir / "subs.mxf";
3074 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3075 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3076 writer.WriteTimedTextResource (dcp::String::compose(
3077 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3078 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3079 "<Id>urn:uuid:%1</Id>"
3080 "<ContentTitleText>Content</ContentTitleText>"
3081 "<AnnotationText>Annotation</AnnotationText>"
3082 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3083 "<ReelNumber>1</ReelNumber>"
3084 "<Language>en-US</Language>"
3085 "<EditRate>25 1</EditRate>"
3086 "<TimeCodeRate>25</TimeCodeRate>"
3087 "<StartTime>00:00:00:00</StartTime>"
3089 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3090 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3091 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3100 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3101 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3103 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3105 check_verify_result (
3108 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3109 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3110 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3116 /** Check a DCP with a 3D asset marked as 2D */
3117 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3119 check_verify_result (
3120 { private_test / "data" / "xm" },
3123 dcp::VerificationNote::Type::WARNING,
3124 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3127 dcp::VerificationNote::Type::BV21_ERROR,
3128 dcp::VerificationNote::Code::INVALID_STANDARD
3135 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3137 path dir = "build/test/verify_unexpected_things_in_main_markers";
3138 prepare_directory (dir);
3139 auto dcp = make_simple (dir, 1, 24);
3140 dcp->set_annotation_text("A Test DCP");
3144 Editor e (find_cpl(dir));
3146 " <IntrinsicDuration>24</IntrinsicDuration>",
3147 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3151 dcp::CPL cpl (find_cpl(dir));
3153 check_verify_result (
3156 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3157 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3158 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },