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 = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
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);
149 dcp::String::compose("libdcp %1", dcp::version),
150 dcp::String::compose("libdcp %1", dcp::version),
151 dcp::LocalTime().as_string(),
159 /** Class that can alter a file by searching and replacing strings within it.
160 * On destruction modifies the file whose name was given to the constructor.
168 _content = dcp::file_to_string (_path);
173 auto f = fopen(_path.string().c_str(), "w");
175 fwrite (_content.c_str(), _content.length(), 1, f);
179 void replace (string a, string b)
181 auto old_content = _content;
182 boost::algorithm::replace_all (_content, a, b);
183 BOOST_REQUIRE (_content != old_content);
186 void delete_first_line_containing (string s)
188 vector<string> lines;
189 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
190 auto old_content = _content;
193 for (auto i: lines) {
194 if (i.find(s) == string::npos || done) {
195 _content += i + "\n";
200 BOOST_REQUIRE (_content != old_content);
203 void delete_lines (string from, string to)
205 vector<string> lines;
206 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
207 bool deleting = false;
208 auto old_content = _content;
210 for (auto i: lines) {
211 if (i.find(from) != string::npos) {
215 _content += i + "\n";
217 if (deleting && i.find(to) != string::npos) {
221 BOOST_REQUIRE (_content != old_content);
224 void insert (string after, string line)
226 vector<string> lines;
227 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
228 auto old_content = _content;
230 bool replaced = false;
231 for (auto i: lines) {
233 if (!replaced && i.find(after) != string::npos) {
238 BOOST_REQUIRE (_content != old_content);
243 std::string _content;
247 LIBDCP_DISABLE_WARNINGS
250 dump_notes (vector<dcp::VerificationNote> const & notes)
252 for (auto i: notes) {
253 std::cout << dcp::note_to_string(i) << "\n";
256 LIBDCP_ENABLE_WARNINGS
261 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
263 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
264 std::sort (notes.begin(), notes.end());
265 std::sort (test_notes.begin(), test_notes.end());
267 string message = "\nVerification notes from test:\n";
268 for (auto i: notes) {
269 message += " " + note_to_string(i) + "\n";
271 message += "Expected:\n";
272 for (auto i: test_notes) {
273 message += " " + note_to_string(i) + "\n";
276 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
280 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
281 * replacing from with to. Verify the resulting DCP and check that the results match the given
286 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
288 auto dir = setup (1, suffix);
291 Editor e (file(suffix));
292 e.replace (from, to);
295 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
297 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
298 auto i = notes.begin();
299 auto j = codes.begin();
300 while (i != notes.end()) {
301 BOOST_CHECK_EQUAL (i->code(), *j);
308 BOOST_AUTO_TEST_CASE (verify_no_error)
311 auto dir = setup (1, "no_error");
312 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
314 path const cpl_file = dir / dcp_test1_cpl;
315 path const pkl_file = dir / dcp_test1_pkl;
316 path const assetmap_file = dir / "ASSETMAP.xml";
318 auto st = stages.begin();
319 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
320 BOOST_REQUIRE (st->second);
321 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
323 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
324 BOOST_REQUIRE (st->second);
325 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
327 BOOST_CHECK_EQUAL (st->first, "Checking reel");
328 BOOST_REQUIRE (!st->second);
330 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
331 BOOST_REQUIRE (st->second);
332 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
334 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
335 BOOST_REQUIRE (st->second);
336 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
338 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
339 BOOST_REQUIRE (st->second);
340 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
342 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
343 BOOST_REQUIRE (st->second);
344 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
346 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
347 BOOST_REQUIRE (st->second);
348 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
350 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
351 BOOST_REQUIRE (st->second);
352 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
354 BOOST_REQUIRE (st == stages.end());
356 BOOST_CHECK_EQUAL (notes.size(), 0);
360 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
362 using namespace boost::filesystem;
364 auto dir = setup (1, "incorrect_picture_sound_hash");
366 auto video_path = path(dir / "video.mxf");
367 auto mod = fopen(video_path.string().c_str(), "r+b");
369 fseek (mod, 4096, SEEK_SET);
371 fwrite (&x, sizeof(x), 1, mod);
374 auto audio_path = path(dir / "audio.mxf");
375 mod = fopen(audio_path.string().c_str(), "r+b");
377 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
378 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
381 dcp::ASDCPErrorSuspender sus;
382 check_verify_result (
385 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
386 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
391 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
393 using namespace boost::filesystem;
395 auto dir = setup (1, "mismatched_picture_sound_hashes");
398 Editor e (dir / dcp_test1_pkl);
399 e.replace ("<Hash>", "<Hash>x");
402 check_verify_result (
405 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
406 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
407 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
408 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xLq7ot/GobgrqUYdlbR8FCD5APqs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
409 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xgVKhC9IkWyzQbgzpFcJ1bpqbtwk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
410 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xznqYbl53W9ZQtrU2E1FQ6dwdM2M=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
415 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
417 auto dir = setup (1, "failed_read_content_kind");
420 Editor e (dir / dcp_test1_cpl);
421 e.replace ("<ContentKind>", "<ContentKind>x");
424 check_verify_result (
426 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
435 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
443 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
449 asset_map (string suffix)
451 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
455 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
457 check_verify_result_after_replace (
458 "invalid_picture_frame_rate", &cpl,
459 "<FrameRate>24 1", "<FrameRate>99 1",
460 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
461 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
465 BOOST_AUTO_TEST_CASE (verify_missing_asset)
467 auto dir = setup (1, "missing_asset");
468 remove (dir / "video.mxf");
469 check_verify_result (
472 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
477 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
479 check_verify_result_after_replace (
480 "empty_asset_path", &asset_map,
481 "<Path>video.mxf</Path>", "<Path></Path>",
482 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
487 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
489 check_verify_result_after_replace (
490 "mismatched_standard", &cpl,
491 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
492 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
493 dcp::VerificationNote::Code::INVALID_XML,
494 dcp::VerificationNote::Code::INVALID_XML,
495 dcp::VerificationNote::Code::INVALID_XML,
496 dcp::VerificationNote::Code::INVALID_XML,
497 dcp::VerificationNote::Code::INVALID_XML,
498 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
503 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
505 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
506 check_verify_result_after_replace (
507 "invalid_xml_cpl_id", &cpl,
508 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
509 { dcp::VerificationNote::Code::INVALID_XML }
514 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
516 check_verify_result_after_replace (
517 "invalid_xml_issue_date", &cpl,
518 "<IssueDate>", "<IssueDate>x",
519 { dcp::VerificationNote::Code::INVALID_XML,
520 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
525 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
527 check_verify_result_after_replace (
528 "invalid_xml_pkl_id", &pkl,
529 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
530 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
531 { dcp::VerificationNote::Code::INVALID_XML }
536 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
538 check_verify_result_after_replace (
539 "invalid_xml_asset_map_id", &asset_map,
540 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
541 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
542 { dcp::VerificationNote::Code::INVALID_XML }
547 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
550 auto dir = setup (3, "verify_invalid_standard");
551 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
553 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
554 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
555 path const assetmap_file = dir / "ASSETMAP";
557 auto st = stages.begin();
558 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
559 BOOST_REQUIRE (st->second);
560 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
562 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
563 BOOST_REQUIRE (st->second);
564 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
566 BOOST_CHECK_EQUAL (st->first, "Checking reel");
567 BOOST_REQUIRE (!st->second);
569 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
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 picture frame sizes");
574 BOOST_REQUIRE (st->second);
575 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
577 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
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 sound asset metadata");
582 BOOST_REQUIRE (st->second);
583 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
585 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
586 BOOST_REQUIRE (st->second);
587 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
589 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
590 BOOST_REQUIRE (st->second);
591 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
593 BOOST_REQUIRE (st == stages.end());
595 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
596 auto i = notes.begin ();
597 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
598 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
600 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
601 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
604 /* DCP with a short asset */
605 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
607 auto dir = setup (8, "invalid_duration");
608 check_verify_result (
611 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
613 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
614 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
615 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
616 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
623 dcp_from_frame (dcp::ArrayData const& frame, path dir)
625 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
626 create_directories (dir);
627 auto writer = asset->start_write (dir / "pic.mxf", true);
628 for (int i = 0; i < 24; ++i) {
629 writer->write (frame.data(), frame.size());
633 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
634 return write_dcp_with_single_asset (dir, reel_asset);
638 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
640 int const too_big = 1302083 * 2;
642 /* Compress a black image */
643 auto image = black_image ();
644 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
645 BOOST_REQUIRE (frame.size() < too_big);
647 /* Place it in a bigger block with some zero padding at the end */
648 dcp::ArrayData oversized_frame(too_big);
649 memcpy (oversized_frame.data(), frame.data(), frame.size());
650 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
652 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
653 prepare_directory (dir);
654 auto cpl = dcp_from_frame (oversized_frame, dir);
656 check_verify_result (
659 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
660 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
661 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
666 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
668 int const nearly_too_big = 1302083 * 0.98;
670 /* Compress a black image */
671 auto image = black_image ();
672 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
673 BOOST_REQUIRE (frame.size() < nearly_too_big);
675 /* Place it in a bigger block with some zero padding at the end */
676 dcp::ArrayData oversized_frame(nearly_too_big);
677 memcpy (oversized_frame.data(), frame.data(), frame.size());
678 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
680 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
681 prepare_directory (dir);
682 auto cpl = dcp_from_frame (oversized_frame, dir);
684 check_verify_result (
687 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
694 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
696 /* Compress a black image */
697 auto image = black_image ();
698 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
699 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
701 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
702 prepare_directory (dir);
703 auto cpl = dcp_from_frame (frame, dir);
705 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
709 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
711 path const dir("build/test/verify_valid_interop_subtitles");
712 prepare_directory (dir);
713 copy_file ("test/data/subs1.xml", dir / "subs.xml");
714 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
715 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
716 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
718 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
722 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
724 using namespace boost::filesystem;
726 path const dir("build/test/verify_invalid_interop_subtitles");
727 prepare_directory (dir);
728 copy_file ("test/data/subs1.xml", dir / "subs.xml");
729 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
730 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
731 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
734 Editor e (dir / "subs.xml");
735 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
738 check_verify_result (
741 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
742 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
744 dcp::VerificationNote::Type::ERROR,
745 dcp::VerificationNote::Code::INVALID_XML,
746 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
754 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
756 path const dir("build/test/verify_valid_smpte_subtitles");
757 prepare_directory (dir);
758 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
759 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
760 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
761 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
763 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
767 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
769 using namespace boost::filesystem;
771 path const dir("build/test/verify_invalid_smpte_subtitles");
772 prepare_directory (dir);
773 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
774 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
775 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
776 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
777 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
779 check_verify_result (
782 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
784 dcp::VerificationNote::Type::ERROR,
785 dcp::VerificationNote::Code::INVALID_XML,
786 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
790 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
791 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
796 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
798 path const dir("build/test/verify_empty_text_node_in_subtitles");
799 prepare_directory (dir);
800 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
801 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
802 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
803 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
805 check_verify_result (
808 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
809 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
810 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
811 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
816 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
817 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
819 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
820 prepare_directory (dir);
821 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
822 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
823 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
824 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
826 check_verify_result (
829 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
834 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
835 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
837 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
838 prepare_directory (dir);
839 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
840 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
841 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
842 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
844 check_verify_result (
847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
848 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
853 BOOST_AUTO_TEST_CASE (verify_external_asset)
855 path const ov_dir("build/test/verify_external_asset");
856 prepare_directory (ov_dir);
858 auto image = black_image ();
859 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
860 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
861 dcp_from_frame (frame, ov_dir);
863 dcp::DCP ov (ov_dir);
866 path const vf_dir("build/test/verify_external_asset_vf");
867 prepare_directory (vf_dir);
869 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
870 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
872 check_verify_result (
875 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
876 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
881 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
883 path const dir("build/test/verify_valid_cpl_metadata");
884 prepare_directory (dir);
886 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
887 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
888 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
890 auto reel = make_shared<dcp::Reel>();
891 reel->add (reel_asset);
893 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
894 reel->add (simple_markers(16 * 24));
896 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
898 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
899 cpl->set_main_sound_sample_rate (48000);
900 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
901 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
902 cpl->set_version_number (1);
907 dcp::String::compose("libdcp %1", dcp::version),
908 dcp::String::compose("libdcp %1", dcp::version),
909 dcp::LocalTime().as_string(),
915 path find_cpl (path dir)
917 for (auto i: directory_iterator(dir)) {
918 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
923 BOOST_REQUIRE (false);
928 /* DCP with invalid CompositionMetadataAsset */
929 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
931 using namespace boost::filesystem;
933 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
934 prepare_directory (dir);
936 auto reel = make_shared<dcp::Reel>();
937 reel->add (black_picture_asset(dir));
938 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
940 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
941 cpl->set_main_sound_sample_rate (48000);
942 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
943 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
944 cpl->set_version_number (1);
946 reel->add (simple_markers());
951 dcp::String::compose("libdcp %1", dcp::version),
952 dcp::String::compose("libdcp %1", dcp::version),
953 dcp::LocalTime().as_string(),
958 Editor e (find_cpl(dir));
959 e.replace ("MainSound", "MainSoundX");
962 check_verify_result (
965 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
966 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
968 dcp::VerificationNote::Type::ERROR,
969 dcp::VerificationNote::Code::INVALID_XML,
970 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
971 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
972 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
973 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
974 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
975 "ExtensionMetadataList?,)'"),
976 canonical(cpl->file().get()),
979 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
984 /* DCP with invalid CompositionMetadataAsset */
985 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
987 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
988 prepare_directory (dir);
990 auto reel = make_shared<dcp::Reel>();
991 reel->add (black_picture_asset(dir));
992 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
994 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
995 cpl->set_main_sound_sample_rate (48000);
996 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
997 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1002 dcp::String::compose("libdcp %1", dcp::version),
1003 dcp::String::compose("libdcp %1", dcp::version),
1004 dcp::LocalTime().as_string(),
1009 Editor e (find_cpl(dir));
1010 e.replace ("meta:Width", "meta:WidthX");
1013 check_verify_result (
1015 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1020 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1022 path const dir("build/test/verify_invalid_language1");
1023 prepare_directory (dir);
1024 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1025 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1026 asset->_language = "wrong-andbad";
1027 asset->write (dir / "subs.mxf");
1028 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1029 reel_asset->_language = "badlang";
1030 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1032 check_verify_result (
1035 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1036 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1037 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1042 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1043 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1045 path const dir("build/test/verify_invalid_language2");
1046 prepare_directory (dir);
1047 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1048 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1049 asset->_language = "wrong-andbad";
1050 asset->write (dir / "subs.mxf");
1051 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1052 reel_asset->_language = "badlang";
1053 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1055 check_verify_result (
1058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1059 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1060 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1065 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1066 * the release territory.
1068 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1070 path const dir("build/test/verify_invalid_language3");
1071 prepare_directory (dir);
1073 auto picture = simple_picture (dir, "foo");
1074 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1075 auto reel = make_shared<dcp::Reel>();
1076 reel->add (reel_picture);
1077 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1078 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1079 reel->add (reel_sound);
1080 reel->add (simple_markers());
1082 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1084 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1085 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1086 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1087 cpl->set_main_sound_sample_rate (48000);
1088 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1089 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1090 cpl->set_version_number (1);
1091 cpl->_release_territory = "fred-jim";
1092 auto dcp = make_shared<dcp::DCP>(dir);
1095 dcp::String::compose("libdcp %1", dcp::version),
1096 dcp::String::compose("libdcp %1", dcp::version),
1097 dcp::LocalTime().as_string(),
1101 check_verify_result (
1104 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1105 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1106 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1107 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1113 vector<dcp::VerificationNote>
1114 check_picture_size (int width, int height, int frame_rate, bool three_d)
1116 using namespace boost::filesystem;
1118 path dcp_path = "build/test/verify_picture_test";
1119 prepare_directory (dcp_path);
1121 shared_ptr<dcp::PictureAsset> mp;
1123 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1125 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1127 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1129 auto image = black_image (dcp::Size(width, height));
1130 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1131 int const length = three_d ? frame_rate * 2 : frame_rate;
1132 for (int i = 0; i < length; ++i) {
1133 picture_writer->write (j2c.data(), j2c.size());
1135 picture_writer->finalize ();
1137 auto d = make_shared<dcp::DCP>(dcp_path);
1138 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1139 cpl->set_annotation_text ("A Test DCP");
1140 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1141 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1142 cpl->set_main_sound_sample_rate (48000);
1143 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1144 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1145 cpl->set_version_number (1);
1147 auto reel = make_shared<dcp::Reel>();
1150 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1152 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1155 reel->add (simple_markers(frame_rate));
1161 dcp::String::compose("libdcp %1", dcp::version),
1162 dcp::String::compose("libdcp %1", dcp::version),
1163 dcp::LocalTime().as_string(),
1167 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1173 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1175 auto notes = check_picture_size(width, height, frame_rate, three_d);
1176 BOOST_CHECK_EQUAL (notes.size(), 0U);
1182 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1184 auto notes = check_picture_size(width, height, frame_rate, three_d);
1185 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1186 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1187 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1193 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1195 auto notes = check_picture_size(width, height, frame_rate, three_d);
1196 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1197 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1198 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1204 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1206 auto notes = check_picture_size(width, height, frame_rate, three_d);
1207 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1208 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1209 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1213 BOOST_AUTO_TEST_CASE (verify_picture_size)
1215 using namespace boost::filesystem;
1218 check_picture_size_ok (2048, 858, 24, false);
1219 check_picture_size_ok (2048, 858, 25, false);
1220 check_picture_size_ok (2048, 858, 48, false);
1221 check_picture_size_ok (2048, 858, 24, true);
1222 check_picture_size_ok (2048, 858, 25, true);
1223 check_picture_size_ok (2048, 858, 48, true);
1226 check_picture_size_ok (1998, 1080, 24, false);
1227 check_picture_size_ok (1998, 1080, 25, false);
1228 check_picture_size_ok (1998, 1080, 48, false);
1229 check_picture_size_ok (1998, 1080, 24, true);
1230 check_picture_size_ok (1998, 1080, 25, true);
1231 check_picture_size_ok (1998, 1080, 48, true);
1234 check_picture_size_ok (4096, 1716, 24, false);
1237 check_picture_size_ok (3996, 2160, 24, false);
1239 /* Bad frame size */
1240 check_picture_size_bad_frame_size (2050, 858, 24, false);
1241 check_picture_size_bad_frame_size (2048, 658, 25, false);
1242 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1243 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1245 /* Bad 2K frame rate */
1246 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1247 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1248 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1250 /* Bad 4K frame rate */
1251 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1252 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1255 auto notes = check_picture_size(3996, 2160, 24, true);
1256 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1257 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1258 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1264 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")
1267 make_shared<dcp::SubtitleString>(
1275 dcp::Time(start_frame, 24, 24),
1276 dcp::Time(end_frame, 24, 24),
1278 dcp::HAlign::CENTER,
1281 dcp::Direction::LTR,
1293 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1295 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1296 prepare_directory (dir);
1298 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1299 for (int i = 0; i < 2048; ++i) {
1300 add_test_subtitle (asset, i * 24, i * 24 + 20);
1302 asset->set_language (dcp::LanguageTag("de-DE"));
1303 asset->write (dir / "subs.mxf");
1304 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1305 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1307 check_verify_result (
1310 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1312 dcp::VerificationNote::Type::BV21_ERROR,
1313 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1315 canonical(dir / "subs.mxf")
1317 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1318 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1324 shared_ptr<dcp::SMPTESubtitleAsset>
1325 make_large_subtitle_asset (path font_file)
1327 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1328 dcp::ArrayData big_fake_font(1024 * 1024);
1329 big_fake_font.write (font_file);
1330 for (int i = 0; i < 116; ++i) {
1331 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1339 verify_timed_text_asset_too_large (string name)
1341 auto const dir = path("build/test") / name;
1342 prepare_directory (dir);
1343 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1344 add_test_subtitle (asset, 0, 240);
1345 asset->set_language (dcp::LanguageTag("de-DE"));
1346 asset->write (dir / "subs.mxf");
1348 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1349 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1351 check_verify_result (
1354 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1355 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1356 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1357 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1358 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1363 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1365 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1366 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1370 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1372 path dir = "build/test/verify_missing_subtitle_language";
1373 prepare_directory (dir);
1374 auto dcp = make_simple (dir, 1, 106);
1377 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1378 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1379 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1380 "<ContentTitleText>Content</ContentTitleText>"
1381 "<AnnotationText>Annotation</AnnotationText>"
1382 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1383 "<ReelNumber>1</ReelNumber>"
1384 "<EditRate>24 1</EditRate>"
1385 "<TimeCodeRate>24</TimeCodeRate>"
1386 "<StartTime>00:00:00:00</StartTime>"
1387 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1389 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1390 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1391 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1397 dcp::File xml_file(dir / "subs.xml", "w");
1398 BOOST_REQUIRE (xml_file);
1399 xml_file.write(xml.c_str(), xml.size(), 1);
1401 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1402 subs->write (dir / "subs.mxf");
1404 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1405 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1407 dcp::String::compose("libdcp %1", dcp::version),
1408 dcp::String::compose("libdcp %1", dcp::version),
1409 dcp::LocalTime().as_string(),
1413 check_verify_result (
1416 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1417 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1422 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1424 path path ("build/test/verify_mismatched_subtitle_languages");
1425 auto constexpr reel_length = 192;
1426 auto dcp = make_simple (path, 2, reel_length);
1427 auto cpl = dcp->cpls()[0];
1430 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1431 subs->set_language (dcp::LanguageTag("de-DE"));
1432 subs->add (simple_subtitle());
1433 subs->write (path / "subs1.mxf");
1434 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1435 cpl->reels()[0]->add(reel_subs);
1439 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1440 subs->set_language (dcp::LanguageTag("en-US"));
1441 subs->add (simple_subtitle());
1442 subs->write (path / "subs2.mxf");
1443 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1444 cpl->reels()[1]->add(reel_subs);
1448 dcp::String::compose("libdcp %1", dcp::version),
1449 dcp::String::compose("libdcp %1", dcp::version),
1450 dcp::LocalTime().as_string(),
1454 check_verify_result (
1457 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1458 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1459 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1464 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1466 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1467 auto constexpr reel_length = 192;
1468 auto dcp = make_simple (path, 2, reel_length);
1469 auto cpl = dcp->cpls()[0];
1472 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1473 ccaps->set_language (dcp::LanguageTag("de-DE"));
1474 ccaps->add (simple_subtitle());
1475 ccaps->write (path / "subs1.mxf");
1476 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1477 cpl->reels()[0]->add(reel_ccaps);
1481 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1482 ccaps->set_language (dcp::LanguageTag("en-US"));
1483 ccaps->add (simple_subtitle());
1484 ccaps->write (path / "subs2.mxf");
1485 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1486 cpl->reels()[1]->add(reel_ccaps);
1490 dcp::String::compose("libdcp %1", dcp::version),
1491 dcp::String::compose("libdcp %1", dcp::version),
1492 dcp::LocalTime().as_string(),
1496 check_verify_result (
1499 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1500 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1505 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1507 path dir = "build/test/verify_missing_subtitle_start_time";
1508 prepare_directory (dir);
1509 auto dcp = make_simple (dir, 1, 106);
1512 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1513 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1514 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1515 "<ContentTitleText>Content</ContentTitleText>"
1516 "<AnnotationText>Annotation</AnnotationText>"
1517 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1518 "<ReelNumber>1</ReelNumber>"
1519 "<Language>de-DE</Language>"
1520 "<EditRate>24 1</EditRate>"
1521 "<TimeCodeRate>24</TimeCodeRate>"
1522 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1524 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1525 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1526 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1532 dcp::File xml_file(dir / "subs.xml", "w");
1533 BOOST_REQUIRE (xml_file);
1534 xml_file.write(xml.c_str(), xml.size(), 1);
1536 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1537 subs->write (dir / "subs.mxf");
1539 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1540 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1542 dcp::String::compose("libdcp %1", dcp::version),
1543 dcp::String::compose("libdcp %1", dcp::version),
1544 dcp::LocalTime().as_string(),
1548 check_verify_result (
1551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1552 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1557 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1559 path dir = "build/test/verify_invalid_subtitle_start_time";
1560 prepare_directory (dir);
1561 auto dcp = make_simple (dir, 1, 106);
1564 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1565 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1566 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1567 "<ContentTitleText>Content</ContentTitleText>"
1568 "<AnnotationText>Annotation</AnnotationText>"
1569 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1570 "<ReelNumber>1</ReelNumber>"
1571 "<Language>de-DE</Language>"
1572 "<EditRate>24 1</EditRate>"
1573 "<TimeCodeRate>24</TimeCodeRate>"
1574 "<StartTime>00:00:02:00</StartTime>"
1575 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1577 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1578 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1579 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1585 dcp::File xml_file(dir / "subs.xml", "w");
1586 BOOST_REQUIRE (xml_file);
1587 xml_file.write(xml.c_str(), xml.size(), 1);
1589 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1590 subs->write (dir / "subs.mxf");
1592 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1593 dcp->cpls().front()->reels().front()->add(reel_subs);
1595 dcp::String::compose("libdcp %1", dcp::version),
1596 dcp::String::compose("libdcp %1", dcp::version),
1597 dcp::LocalTime().as_string(),
1601 check_verify_result (
1604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1605 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1613 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1616 , v_position(v_position_)
1624 dcp::VAlign v_align;
1630 shared_ptr<dcp::CPL>
1631 dcp_with_text (path dir, vector<TestText> subs)
1633 prepare_directory (dir);
1634 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1635 asset->set_start_time (dcp::Time());
1636 for (auto i: subs) {
1637 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1639 asset->set_language (dcp::LanguageTag("de-DE"));
1640 asset->write (dir / "subs.mxf");
1642 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1643 return write_dcp_with_single_asset (dir, reel_asset);
1648 shared_ptr<dcp::CPL>
1649 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1651 prepare_directory (dir);
1652 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1653 asset->set_start_time (dcp::Time());
1654 asset->set_language (dcp::LanguageTag("de-DE"));
1656 auto subs_mxf = dir / "subs.mxf";
1657 asset->write (subs_mxf);
1659 /* The call to write() puts the asset into the DCP correctly but it will have
1660 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1663 ASDCP::TimedText::MXFWriter writer;
1664 ASDCP::WriterInfo writer_info;
1665 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1667 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1668 DCP_ASSERT (c == Kumu::UUID_Length);
1669 ASDCP::TimedText::TimedTextDescriptor descriptor;
1670 descriptor.ContainerDuration = asset->intrinsic_duration();
1671 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1672 DCP_ASSERT (c == Kumu::UUID_Length);
1673 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1674 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1675 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1676 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1679 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1680 return write_dcp_with_single_asset (dir, reel_asset);
1684 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1686 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1687 /* Just too early */
1688 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1689 check_verify_result (
1692 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1699 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1701 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1702 /* Just late enough */
1703 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1704 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1708 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1710 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1711 prepare_directory (dir);
1713 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1714 asset1->set_start_time (dcp::Time());
1715 /* Just late enough */
1716 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1717 asset1->set_language (dcp::LanguageTag("de-DE"));
1718 asset1->write (dir / "subs1.mxf");
1719 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1720 auto reel1 = make_shared<dcp::Reel>();
1721 reel1->add (reel_asset1);
1722 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1723 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1724 reel1->add (markers1);
1726 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1727 asset2->set_start_time (dcp::Time());
1728 /* This would be too early on first reel but should be OK on the second */
1729 add_test_subtitle (asset2, 3, 4 * 24);
1730 asset2->set_language (dcp::LanguageTag("de-DE"));
1731 asset2->write (dir / "subs2.mxf");
1732 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1733 auto reel2 = make_shared<dcp::Reel>();
1734 reel2->add (reel_asset2);
1735 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1736 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1737 reel2->add (markers2);
1739 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1742 auto dcp = make_shared<dcp::DCP>(dir);
1745 dcp::String::compose("libdcp %1", dcp::version),
1746 dcp::String::compose("libdcp %1", dcp::version),
1747 dcp::LocalTime().as_string(),
1752 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1756 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1758 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1759 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1763 { 5 * 24 + 1, 6 * 24 },
1765 check_verify_result (
1768 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1774 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1776 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1777 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1781 { 5 * 24 + 16, 8 * 24 },
1783 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1787 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1789 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1790 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1791 check_verify_result (
1794 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1795 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1800 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1802 auto const dir = path("build/test/verify_valid_subtitle_duration");
1803 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1804 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1808 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1810 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1811 prepare_directory (dir);
1812 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1813 asset->set_start_time (dcp::Time());
1814 add_test_subtitle (asset, 0, 4 * 24);
1815 asset->set_language (dcp::LanguageTag("de-DE"));
1816 asset->write (dir / "subs.mxf");
1818 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1819 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1820 check_verify_result (
1823 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1824 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1825 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1826 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1832 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1834 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1835 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1838 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1839 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1840 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1841 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1843 check_verify_result (
1846 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1852 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1854 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1855 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1858 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1859 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1860 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1862 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1866 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1868 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1869 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1872 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1873 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1874 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1875 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1877 check_verify_result (
1880 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1881 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1886 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1888 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1889 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1892 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1893 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1894 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1895 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1897 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1901 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1903 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1904 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1907 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1909 check_verify_result (
1912 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1918 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1920 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1921 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1924 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1926 check_verify_result (
1929 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1935 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1937 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1938 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1941 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1942 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1943 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1944 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1946 check_verify_result (
1949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1950 { 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_count2)
1957 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1958 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1961 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1962 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1963 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1965 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1969 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1971 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1972 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1975 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1976 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1977 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1978 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1980 check_verify_result (
1983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1989 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1991 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1992 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1995 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1996 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1997 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1998 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
2000 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2004 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2006 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2007 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2010 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2012 check_verify_result (
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2020 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2022 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2023 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2026 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2028 check_verify_result (
2031 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2032 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2037 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2039 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2040 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2043 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2044 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2045 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2047 check_verify_result (
2050 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2055 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2057 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2058 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2061 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2062 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2063 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
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_ordering1)
2076 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2077 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2080 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2081 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2082 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2084 check_verify_result (
2087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2092 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2094 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2095 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2098 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2099 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2100 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2102 check_verify_result (
2105 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2110 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2112 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2113 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2114 check_verify_result (
2117 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2118 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2123 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2125 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2126 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2127 check_verify_result (
2130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2136 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2138 path const dir("build/test/verify_invalid_sound_frame_rate");
2139 prepare_directory (dir);
2141 auto picture = simple_picture (dir, "foo");
2142 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2143 auto reel = make_shared<dcp::Reel>();
2144 reel->add (reel_picture);
2145 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2146 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2147 reel->add (reel_sound);
2148 reel->add (simple_markers());
2149 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2151 auto dcp = make_shared<dcp::DCP>(dir);
2154 dcp::String::compose("libdcp %1", dcp::version),
2155 dcp::String::compose("libdcp %1", dcp::version),
2156 dcp::LocalTime().as_string(),
2160 check_verify_result (
2163 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2164 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2169 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2171 path const dir("build/test/verify_missing_cpl_annotation_text");
2172 auto dcp = make_simple (dir);
2174 dcp::String::compose("libdcp %1", dcp::version),
2175 dcp::String::compose("libdcp %1", dcp::version),
2176 dcp::LocalTime().as_string(),
2180 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2182 auto const cpl = dcp->cpls()[0];
2185 BOOST_REQUIRE (cpl->file());
2186 Editor e(cpl->file().get());
2187 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2190 check_verify_result (
2193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2194 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2199 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2201 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2202 auto dcp = make_simple (dir);
2204 dcp::String::compose("libdcp %1", dcp::version),
2205 dcp::String::compose("libdcp %1", dcp::version),
2206 dcp::LocalTime().as_string(),
2210 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2211 auto const cpl = dcp->cpls()[0];
2214 BOOST_REQUIRE (cpl->file());
2215 Editor e(cpl->file().get());
2216 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2219 check_verify_result (
2222 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2223 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2228 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2230 path const dir("build/test/verify_mismatched_asset_duration");
2231 prepare_directory (dir);
2232 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2233 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2235 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2236 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2238 auto reel = make_shared<dcp::Reel>(
2239 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2240 make_shared<dcp::ReelSoundAsset>(ms, 0)
2243 reel->add (simple_markers());
2248 dcp::String::compose("libdcp %1", dcp::version),
2249 dcp::String::compose("libdcp %1", dcp::version),
2250 dcp::LocalTime().as_string(),
2254 check_verify_result (
2257 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2258 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2265 shared_ptr<dcp::CPL>
2266 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2268 prepare_directory (dir);
2269 auto dcp = make_shared<dcp::DCP>(dir);
2270 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2272 auto constexpr reel_length = 192;
2274 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2275 subs->set_language (dcp::LanguageTag("de-DE"));
2276 subs->set_start_time (dcp::Time());
2277 subs->add (simple_subtitle());
2278 subs->write (dir / "subs.mxf");
2279 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2281 auto reel1 = make_shared<dcp::Reel>(
2282 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2283 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2287 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2290 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2291 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2292 reel1->add (markers1);
2296 auto reel2 = make_shared<dcp::Reel>(
2297 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2298 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2302 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2305 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2306 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2307 reel2->add (markers2);
2313 dcp::String::compose("libdcp %1", dcp::version),
2314 dcp::String::compose("libdcp %1", dcp::version),
2315 dcp::LocalTime().as_string(),
2323 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2326 path dir ("build/test/missing_main_subtitle_from_some_reels");
2327 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2328 check_verify_result (
2331 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2338 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2339 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2340 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2344 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2345 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2346 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2352 shared_ptr<dcp::CPL>
2353 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2355 prepare_directory (dir);
2356 auto dcp = make_shared<dcp::DCP>(dir);
2357 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2359 auto constexpr reel_length = 192;
2361 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2362 subs->set_language (dcp::LanguageTag("de-DE"));
2363 subs->set_start_time (dcp::Time());
2364 subs->add (simple_subtitle());
2365 subs->write (dir / "subs.mxf");
2367 auto reel1 = make_shared<dcp::Reel>(
2368 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2369 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2372 for (int i = 0; i < caps_in_reel1; ++i) {
2373 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2376 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2377 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2378 reel1->add (markers1);
2382 auto reel2 = make_shared<dcp::Reel>(
2383 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2384 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2387 for (int i = 0; i < caps_in_reel2; ++i) {
2388 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2391 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2392 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2393 reel2->add (markers2);
2399 dcp::String::compose("libdcp %1", dcp::version),
2400 dcp::String::compose("libdcp %1", dcp::version),
2401 dcp::LocalTime().as_string(),
2409 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2412 path dir ("build/test/mismatched_closed_caption_asset_counts");
2413 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2414 check_verify_result (
2417 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2423 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2424 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2425 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2429 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2430 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2431 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2438 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2440 prepare_directory (dir);
2441 auto dcp = make_shared<dcp::DCP>(dir);
2442 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2444 auto constexpr reel_length = 192;
2446 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2447 subs->set_language (dcp::LanguageTag("de-DE"));
2448 subs->set_start_time (dcp::Time());
2449 subs->add (simple_subtitle());
2450 subs->write (dir / "subs.mxf");
2451 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2454 auto reel = make_shared<dcp::Reel>(
2455 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2456 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2459 reel->add (reel_text);
2461 reel->add (simple_markers(reel_length));
2467 dcp::String::compose("libdcp %1", dcp::version),
2468 dcp::String::compose("libdcp %1", dcp::version),
2469 dcp::LocalTime().as_string(),
2473 check_verify_result (
2476 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2477 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2482 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2484 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2485 "build/test/verify_subtitle_entry_point_must_be_present",
2486 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2487 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2488 asset->unset_entry_point ();
2492 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2493 "build/test/verify_subtitle_entry_point_must_be_zero",
2494 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2495 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2496 asset->set_entry_point (4);
2500 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2501 "build/test/verify_closed_caption_entry_point_must_be_present",
2502 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2503 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2504 asset->unset_entry_point ();
2508 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2509 "build/test/verify_closed_caption_entry_point_must_be_zero",
2510 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2511 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2512 asset->set_entry_point (9);
2518 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2522 path const dir("build/test/verify_missing_hash");
2523 auto dcp = make_simple (dir);
2525 dcp::String::compose("libdcp %1", dcp::version),
2526 dcp::String::compose("libdcp %1", dcp::version),
2527 dcp::LocalTime().as_string(),
2531 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2532 auto const cpl = dcp->cpls()[0];
2533 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2534 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2535 auto asset_id = cpl->reels()[0]->main_picture()->id();
2538 BOOST_REQUIRE (cpl->file());
2539 Editor e(cpl->file().get());
2540 e.delete_first_line_containing("<Hash>");
2543 check_verify_result (
2546 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2547 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2554 verify_markers_test (
2556 vector<pair<dcp::Marker, dcp::Time>> markers,
2557 vector<dcp::VerificationNote> test_notes
2560 auto dcp = make_simple (dir);
2561 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2562 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2563 for (auto const& i: markers) {
2564 markers_asset->set (i.first, i.second);
2566 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2568 dcp::String::compose("libdcp %1", dcp::version),
2569 dcp::String::compose("libdcp %1", dcp::version),
2570 dcp::LocalTime().as_string(),
2574 check_verify_result ({dir}, test_notes);
2578 BOOST_AUTO_TEST_CASE (verify_markers)
2580 verify_markers_test (
2581 "build/test/verify_markers_all_correct",
2583 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2584 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2585 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2586 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2591 verify_markers_test (
2592 "build/test/verify_markers_missing_ffec",
2594 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2595 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2596 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2599 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2602 verify_markers_test (
2603 "build/test/verify_markers_missing_ffmc",
2605 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2606 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2607 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2610 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2613 verify_markers_test (
2614 "build/test/verify_markers_missing_ffoc",
2616 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2617 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2618 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2621 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2624 verify_markers_test (
2625 "build/test/verify_markers_missing_lfoc",
2627 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2628 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2629 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2632 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2635 verify_markers_test (
2636 "build/test/verify_markers_incorrect_ffoc",
2638 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2639 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2640 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2641 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2644 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2647 verify_markers_test (
2648 "build/test/verify_markers_incorrect_lfoc",
2650 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2651 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2652 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2653 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2656 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2661 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2663 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2664 prepare_directory (dir);
2665 auto dcp = make_simple (dir);
2666 auto cpl = dcp->cpls()[0];
2667 cpl->unset_version_number();
2669 dcp::String::compose("libdcp %1", dcp::version),
2670 dcp::String::compose("libdcp %1", dcp::version),
2671 dcp::LocalTime().as_string(),
2675 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2679 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2681 path dir = "build/test/verify_missing_extension_metadata1";
2682 auto dcp = make_simple (dir);
2684 dcp::String::compose("libdcp %1", dcp::version),
2685 dcp::String::compose("libdcp %1", dcp::version),
2686 dcp::LocalTime().as_string(),
2690 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2691 auto cpl = dcp->cpls()[0];
2694 Editor e (cpl->file().get());
2695 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2698 check_verify_result (
2701 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2702 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2707 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2709 path dir = "build/test/verify_missing_extension_metadata2";
2710 auto dcp = make_simple (dir);
2712 dcp::String::compose("libdcp %1", dcp::version),
2713 dcp::String::compose("libdcp %1", dcp::version),
2714 dcp::LocalTime().as_string(),
2718 auto cpl = dcp->cpls()[0];
2721 Editor e (cpl->file().get());
2722 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2725 check_verify_result (
2728 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2734 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2736 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2737 auto dcp = make_simple (dir);
2739 dcp::String::compose("libdcp %1", dcp::version),
2740 dcp::String::compose("libdcp %1", dcp::version),
2741 dcp::LocalTime().as_string(),
2745 auto const cpl = dcp->cpls()[0];
2748 Editor e (cpl->file().get());
2749 e.replace ("<meta:Name>A", "<meta:NameX>A");
2750 e.replace ("n</meta:Name>", "n</meta:NameX>");
2753 check_verify_result (
2756 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2757 { 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 },
2758 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2763 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2765 path dir = "build/test/verify_invalid_extension_metadata1";
2766 auto dcp = make_simple (dir);
2768 dcp::String::compose("libdcp %1", dcp::version),
2769 dcp::String::compose("libdcp %1", dcp::version),
2770 dcp::LocalTime().as_string(),
2774 auto cpl = dcp->cpls()[0];
2777 Editor e (cpl->file().get());
2778 e.replace ("Application", "Fred");
2781 check_verify_result (
2784 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2790 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2792 path dir = "build/test/verify_invalid_extension_metadata2";
2793 auto dcp = make_simple (dir);
2795 dcp::String::compose("libdcp %1", dcp::version),
2796 dcp::String::compose("libdcp %1", dcp::version),
2797 dcp::LocalTime().as_string(),
2801 auto cpl = dcp->cpls()[0];
2804 Editor e (cpl->file().get());
2805 e.replace ("DCP Constraints Profile", "Fred");
2808 check_verify_result (
2811 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2812 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2817 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2819 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2820 auto dcp = make_simple (dir);
2822 dcp::String::compose("libdcp %1", dcp::version),
2823 dcp::String::compose("libdcp %1", dcp::version),
2824 dcp::LocalTime().as_string(),
2828 auto const cpl = dcp->cpls()[0];
2831 Editor e (cpl->file().get());
2832 e.replace ("<meta:Value>", "<meta:ValueX>");
2833 e.replace ("</meta:Value>", "</meta:ValueX>");
2836 check_verify_result (
2839 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2840 { 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 },
2841 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2846 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2848 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2849 auto dcp = make_simple (dir);
2851 dcp::String::compose("libdcp %1", dcp::version),
2852 dcp::String::compose("libdcp %1", dcp::version),
2853 dcp::LocalTime().as_string(),
2857 auto const cpl = dcp->cpls()[0];
2860 Editor e (cpl->file().get());
2861 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2864 check_verify_result (
2867 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2868 { 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() },
2873 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2875 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2876 auto dcp = make_simple (dir);
2878 dcp::String::compose("libdcp %1", dcp::version),
2879 dcp::String::compose("libdcp %1", dcp::version),
2880 dcp::LocalTime().as_string(),
2884 auto const cpl = dcp->cpls()[0];
2887 Editor e (cpl->file().get());
2888 e.replace ("<meta:Property>", "<meta:PropertyX>");
2889 e.replace ("</meta:Property>", "</meta:PropertyX>");
2892 check_verify_result (
2895 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2896 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2897 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2902 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2904 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2905 auto dcp = make_simple (dir);
2907 dcp::String::compose("libdcp %1", dcp::version),
2908 dcp::String::compose("libdcp %1", dcp::version),
2909 dcp::LocalTime().as_string(),
2913 auto const cpl = dcp->cpls()[0];
2916 Editor e (cpl->file().get());
2917 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2918 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2921 check_verify_result (
2924 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2925 { 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 },
2926 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2932 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2934 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2935 prepare_directory (dir);
2936 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2937 copy_file (i.path(), dir / i.path().filename());
2940 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2941 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2945 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2948 check_verify_result (
2951 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2952 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2953 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2954 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2955 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2956 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2957 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2958 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2963 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2965 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2966 prepare_directory (dir);
2967 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2968 copy_file (i.path(), dir / i.path().filename());
2971 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2972 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2975 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2978 check_verify_result (
2981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2984 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2985 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2992 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2994 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2995 prepare_directory (dir);
2996 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2997 copy_file (i.path(), dir / i.path().filename());
3001 Editor e (dir / dcp_test1_pkl);
3002 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
3005 check_verify_result ({dir}, {});
3009 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
3011 path dir ("build/test/verify_must_not_be_partially_encrypted");
3012 prepare_directory (dir);
3016 auto signer = make_shared<dcp::CertificateChain>();
3017 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
3018 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3019 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3020 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3022 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3026 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3029 auto writer = mp->start_write (dir / "video.mxf", false);
3030 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3031 for (int i = 0; i < 24; ++i) {
3032 writer->write (j2c.data(), j2c.size());
3034 writer->finalize ();
3036 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3038 auto reel = make_shared<dcp::Reel>(
3039 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3040 make_shared<dcp::ReelSoundAsset>(ms, 0)
3043 reel->add (simple_markers());
3047 cpl->set_content_version (
3048 {"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"}
3050 cpl->set_annotation_text ("A Test DCP");
3051 cpl->set_issuer ("OpenDCP 0.0.25");
3052 cpl->set_creator ("OpenDCP 0.0.25");
3053 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3054 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3055 cpl->set_main_sound_sample_rate (48000);
3056 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3057 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3058 cpl->set_version_number (1);
3062 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
3064 check_verify_result (
3067 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3072 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3074 vector<dcp::VerificationNote> notes;
3075 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"));
3076 auto reader = picture.start_read ();
3077 auto frame = reader->get_frame (0);
3078 verify_j2k (frame, notes);
3079 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3083 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3085 vector<dcp::VerificationNote> notes;
3086 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3087 auto reader = picture.start_read ();
3088 auto frame = reader->get_frame (0);
3089 verify_j2k (frame, notes);
3090 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3094 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3096 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3097 prepare_directory (dir);
3098 auto dcp = make_simple (dir);
3100 vector<dcp::VerificationNote> notes;
3101 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3102 auto reader = picture.start_read ();
3103 auto frame = reader->get_frame (0);
3104 verify_j2k (frame, notes);
3105 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3109 /** Check that ResourceID and the XML ID being different is spotted */
3110 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3112 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3113 prepare_directory (dir);
3115 ASDCP::WriterInfo writer_info;
3116 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3119 auto mxf_id = dcp::make_uuid ();
3120 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3121 BOOST_REQUIRE (c == Kumu::UUID_Length);
3123 auto resource_id = dcp::make_uuid ();
3124 ASDCP::TimedText::TimedTextDescriptor descriptor;
3125 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3126 DCP_ASSERT (c == Kumu::UUID_Length);
3128 auto xml_id = dcp::make_uuid ();
3129 ASDCP::TimedText::MXFWriter writer;
3130 auto subs_mxf = dir / "subs.mxf";
3131 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3132 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3133 writer.WriteTimedTextResource (dcp::String::compose(
3134 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3135 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3136 "<Id>urn:uuid:%1</Id>"
3137 "<ContentTitleText>Content</ContentTitleText>"
3138 "<AnnotationText>Annotation</AnnotationText>"
3139 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3140 "<ReelNumber>1</ReelNumber>"
3141 "<Language>en-US</Language>"
3142 "<EditRate>25 1</EditRate>"
3143 "<TimeCodeRate>25</TimeCodeRate>"
3144 "<StartTime>00:00:00:00</StartTime>"
3146 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3147 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3148 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3157 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3158 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3160 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3162 check_verify_result (
3165 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3166 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3167 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3168 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3173 /** Check that ResourceID and the MXF ID being the same is spotted */
3174 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3176 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3177 prepare_directory (dir);
3179 ASDCP::WriterInfo writer_info;
3180 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3183 auto mxf_id = dcp::make_uuid ();
3184 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3185 BOOST_REQUIRE (c == Kumu::UUID_Length);
3187 auto resource_id = mxf_id;
3188 ASDCP::TimedText::TimedTextDescriptor descriptor;
3189 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3190 DCP_ASSERT (c == Kumu::UUID_Length);
3192 auto xml_id = resource_id;
3193 ASDCP::TimedText::MXFWriter writer;
3194 auto subs_mxf = dir / "subs.mxf";
3195 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3196 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3197 writer.WriteTimedTextResource (dcp::String::compose(
3198 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3199 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3200 "<Id>urn:uuid:%1</Id>"
3201 "<ContentTitleText>Content</ContentTitleText>"
3202 "<AnnotationText>Annotation</AnnotationText>"
3203 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3204 "<ReelNumber>1</ReelNumber>"
3205 "<Language>en-US</Language>"
3206 "<EditRate>25 1</EditRate>"
3207 "<TimeCodeRate>25</TimeCodeRate>"
3208 "<StartTime>00:00:00:00</StartTime>"
3210 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3211 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3212 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3221 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3222 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3224 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3226 check_verify_result (
3229 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3231 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3237 /** Check a DCP with a 3D asset marked as 2D */
3238 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3240 check_verify_result (
3241 { private_test / "data" / "xm" },
3244 dcp::VerificationNote::Type::WARNING,
3245 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3248 dcp::VerificationNote::Type::BV21_ERROR,
3249 dcp::VerificationNote::Code::INVALID_STANDARD
3256 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3258 path dir = "build/test/verify_unexpected_things_in_main_markers";
3259 prepare_directory (dir);
3260 auto dcp = make_simple (dir, 1, 24);
3262 dcp::String::compose("libdcp %1", dcp::version),
3263 dcp::String::compose("libdcp %1", dcp::version),
3264 dcp::LocalTime().as_string(),
3269 Editor e (find_cpl(dir));
3271 " <IntrinsicDuration>24</IntrinsicDuration>",
3272 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3276 dcp::CPL cpl (find_cpl(dir));
3278 check_verify_result (
3281 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3282 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3283 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },