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.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_interop_closed_caption_asset.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_closed_caption_asset.h"
51 #include "reel_smpte_subtitle_asset.h"
52 #include "smpte_subtitle_asset.h"
53 #include "stereo_picture_asset.h"
54 #include "stream_operators.h"
58 #include "verify_j2k.h"
59 #include <boost/test/unit_test.hpp>
60 #include <boost/algorithm/string.hpp>
70 using std::make_shared;
71 using boost::optional;
72 using namespace boost::filesystem;
73 using std::shared_ptr;
76 static list<pair<string, optional<path>>> stages;
78 static string filename_to_id(boost::filesystem::path path)
80 return path.string().substr(4, path.string().length() - 8);
83 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
84 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
86 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
87 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
89 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
91 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
92 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
94 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
95 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
98 stage (string s, optional<path> p)
100 stages.push_back (make_pair (s, p));
110 prepare_directory (path path)
112 using namespace boost::filesystem;
114 create_directories (path);
118 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
119 * to make a new sacrifical test DCP.
122 setup (int reference_number, string verify_test_suffix)
124 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
125 prepare_directory (dir);
126 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
127 copy_file (i.path(), dir / i.path().filename());
136 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
138 auto reel = make_shared<dcp::Reel>();
139 reel->add (reel_asset);
140 reel->add (simple_markers());
142 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
144 auto dcp = make_shared<dcp::DCP>(dir);
147 dcp::String::compose("libdcp %1", dcp::version),
148 dcp::String::compose("libdcp %1", dcp::version),
149 dcp::LocalTime().as_string(),
157 /** Class that can alter a file by searching and replacing strings within it.
158 * On destruction modifies the file whose name was given to the constructor.
166 _content = dcp::file_to_string (_path);
171 auto f = fopen(_path.string().c_str(), "w");
173 fwrite (_content.c_str(), _content.length(), 1, f);
177 void replace (string a, string b)
179 auto old_content = _content;
180 boost::algorithm::replace_all (_content, a, b);
181 BOOST_REQUIRE (_content != old_content);
184 void delete_first_line_containing (string s)
186 vector<string> lines;
187 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
188 auto old_content = _content;
191 for (auto i: lines) {
192 if (i.find(s) == string::npos || done) {
193 _content += i + "\n";
198 BOOST_REQUIRE (_content != old_content);
201 void delete_lines (string from, string to)
203 vector<string> lines;
204 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
205 bool deleting = false;
206 auto old_content = _content;
208 for (auto i: lines) {
209 if (i.find(from) != string::npos) {
213 _content += i + "\n";
215 if (deleting && i.find(to) != string::npos) {
219 BOOST_REQUIRE (_content != old_content);
224 std::string _content;
228 LIBDCP_DISABLE_WARNINGS
231 dump_notes (vector<dcp::VerificationNote> const & notes)
233 for (auto i: notes) {
234 std::cout << dcp::note_to_string(i) << "\n";
237 LIBDCP_ENABLE_WARNINGS
242 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
244 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
245 std::sort (notes.begin(), notes.end());
246 std::sort (test_notes.begin(), test_notes.end());
248 string message = "\nVerification notes from test:\n";
249 for (auto i: notes) {
250 message += " " + note_to_string(i) + "\n";
252 message += "Expected:\n";
253 for (auto i: test_notes) {
254 message += " " + note_to_string(i) + "\n";
257 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
261 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
262 * replacing from with to. Verify the resulting DCP and check that the results match the given
267 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
269 auto dir = setup (1, suffix);
272 Editor e (file(suffix));
273 e.replace (from, to);
276 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
278 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
279 auto i = notes.begin();
280 auto j = codes.begin();
281 while (i != notes.end()) {
282 BOOST_CHECK_EQUAL (i->code(), *j);
289 BOOST_AUTO_TEST_CASE (verify_no_error)
292 auto dir = setup (1, "no_error");
293 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
295 path const cpl_file = dir / dcp_test1_cpl;
296 path const pkl_file = dir / dcp_test1_pkl;
297 path const assetmap_file = dir / "ASSETMAP.xml";
299 auto st = stages.begin();
300 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
301 BOOST_REQUIRE (st->second);
302 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
304 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
305 BOOST_REQUIRE (st->second);
306 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
308 BOOST_CHECK_EQUAL (st->first, "Checking reel");
309 BOOST_REQUIRE (!st->second);
311 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
312 BOOST_REQUIRE (st->second);
313 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
315 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
316 BOOST_REQUIRE (st->second);
317 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
319 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
320 BOOST_REQUIRE (st->second);
321 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
323 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
324 BOOST_REQUIRE (st->second);
325 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
327 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
328 BOOST_REQUIRE (st->second);
329 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
331 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
332 BOOST_REQUIRE (st->second);
333 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
335 BOOST_REQUIRE (st == stages.end());
337 BOOST_CHECK_EQUAL (notes.size(), 0);
341 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
343 using namespace boost::filesystem;
345 auto dir = setup (1, "incorrect_picture_sound_hash");
347 auto video_path = path(dir / "video.mxf");
348 auto mod = fopen(video_path.string().c_str(), "r+b");
350 fseek (mod, 4096, SEEK_SET);
352 fwrite (&x, sizeof(x), 1, mod);
355 auto audio_path = path(dir / "audio.mxf");
356 mod = fopen(audio_path.string().c_str(), "r+b");
358 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
359 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
362 dcp::ASDCPErrorSuspender sus;
363 check_verify_result (
366 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
367 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
372 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
374 using namespace boost::filesystem;
376 auto dir = setup (1, "mismatched_picture_sound_hashes");
379 Editor e (dir / dcp_test1_pkl);
380 e.replace ("<Hash>", "<Hash>x");
383 check_verify_result (
386 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
387 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
388 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
389 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xLq7ot/GobgrqUYdlbR8FCD5APqs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
390 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xgVKhC9IkWyzQbgzpFcJ1bpqbtwk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
391 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xznqYbl53W9ZQtrU2E1FQ6dwdM2M=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
396 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
398 auto dir = setup (1, "failed_read_content_kind");
401 Editor e (dir / dcp_test1_cpl);
402 e.replace ("<ContentKind>", "<ContentKind>x");
405 check_verify_result (
407 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
416 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
424 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
430 asset_map (string suffix)
432 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
436 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
438 check_verify_result_after_replace (
439 "invalid_picture_frame_rate", &cpl,
440 "<FrameRate>24 1", "<FrameRate>99 1",
441 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
442 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
446 BOOST_AUTO_TEST_CASE (verify_missing_asset)
448 auto dir = setup (1, "missing_asset");
449 remove (dir / "video.mxf");
450 check_verify_result (
453 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
458 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
460 check_verify_result_after_replace (
461 "empty_asset_path", &asset_map,
462 "<Path>video.mxf</Path>", "<Path></Path>",
463 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
468 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
470 check_verify_result_after_replace (
471 "mismatched_standard", &cpl,
472 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
473 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
474 dcp::VerificationNote::Code::INVALID_XML,
475 dcp::VerificationNote::Code::INVALID_XML,
476 dcp::VerificationNote::Code::INVALID_XML,
477 dcp::VerificationNote::Code::INVALID_XML,
478 dcp::VerificationNote::Code::INVALID_XML,
479 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
484 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
486 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
487 check_verify_result_after_replace (
488 "invalid_xml_cpl_id", &cpl,
489 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
490 { dcp::VerificationNote::Code::INVALID_XML }
495 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
497 check_verify_result_after_replace (
498 "invalid_xml_issue_date", &cpl,
499 "<IssueDate>", "<IssueDate>x",
500 { dcp::VerificationNote::Code::INVALID_XML,
501 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
506 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
508 check_verify_result_after_replace (
509 "invalid_xml_pkl_id", &pkl,
510 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
511 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
512 { dcp::VerificationNote::Code::INVALID_XML }
517 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
519 check_verify_result_after_replace (
520 "invalid_xml_asset_map_id", &asset_map,
521 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
522 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
523 { dcp::VerificationNote::Code::INVALID_XML }
528 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
531 auto dir = setup (3, "verify_invalid_standard");
532 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
534 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
535 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
536 path const assetmap_file = dir / "ASSETMAP";
538 auto st = stages.begin();
539 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
540 BOOST_REQUIRE (st->second);
541 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
543 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
544 BOOST_REQUIRE (st->second);
545 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
547 BOOST_CHECK_EQUAL (st->first, "Checking reel");
548 BOOST_REQUIRE (!st->second);
550 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
551 BOOST_REQUIRE (st->second);
552 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
554 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
555 BOOST_REQUIRE (st->second);
556 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
558 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
559 BOOST_REQUIRE (st->second);
560 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
562 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
563 BOOST_REQUIRE (st->second);
564 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
566 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
567 BOOST_REQUIRE (st->second);
568 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
570 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
571 BOOST_REQUIRE (st->second);
572 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
574 BOOST_REQUIRE (st == stages.end());
576 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
577 auto i = notes.begin ();
578 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
579 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
581 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
582 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
585 /* DCP with a short asset */
586 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
588 auto dir = setup (8, "invalid_duration");
589 check_verify_result (
592 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
593 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
594 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
595 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
596 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
604 dcp_from_frame (dcp::ArrayData const& frame, path dir)
606 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
607 create_directories (dir);
608 auto writer = asset->start_write (dir / "pic.mxf", true);
609 for (int i = 0; i < 24; ++i) {
610 writer->write (frame.data(), frame.size());
614 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
615 return write_dcp_with_single_asset (dir, reel_asset);
619 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
621 int const too_big = 1302083 * 2;
623 /* Compress a black image */
624 auto image = black_image ();
625 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
626 BOOST_REQUIRE (frame.size() < too_big);
628 /* Place it in a bigger block with some zero padding at the end */
629 dcp::ArrayData oversized_frame(too_big);
630 memcpy (oversized_frame.data(), frame.data(), frame.size());
631 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
633 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
634 prepare_directory (dir);
635 auto cpl = dcp_from_frame (oversized_frame, dir);
637 check_verify_result (
640 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
641 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
642 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
647 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
649 int const nearly_too_big = 1302083 * 0.98;
651 /* Compress a black image */
652 auto image = black_image ();
653 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
654 BOOST_REQUIRE (frame.size() < nearly_too_big);
656 /* Place it in a bigger block with some zero padding at the end */
657 dcp::ArrayData oversized_frame(nearly_too_big);
658 memcpy (oversized_frame.data(), frame.data(), frame.size());
659 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
661 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
662 prepare_directory (dir);
663 auto cpl = dcp_from_frame (oversized_frame, dir);
665 check_verify_result (
668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
669 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
670 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
675 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
677 /* Compress a black image */
678 auto image = black_image ();
679 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
680 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
682 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
683 prepare_directory (dir);
684 auto cpl = dcp_from_frame (frame, dir);
686 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
690 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
692 path const dir("build/test/verify_valid_interop_subtitles");
693 prepare_directory (dir);
694 copy_file ("test/data/subs1.xml", dir / "subs.xml");
695 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
696 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
697 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
699 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
703 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
705 using namespace boost::filesystem;
707 path const dir("build/test/verify_invalid_interop_subtitles");
708 prepare_directory (dir);
709 copy_file ("test/data/subs1.xml", dir / "subs.xml");
710 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
711 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
712 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
715 Editor e (dir / "subs.xml");
716 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
719 check_verify_result (
722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
723 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
725 dcp::VerificationNote::Type::ERROR,
726 dcp::VerificationNote::Code::INVALID_XML,
727 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
735 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
737 path const dir("build/test/verify_valid_smpte_subtitles");
738 prepare_directory (dir);
739 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
740 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
741 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
742 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
744 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
748 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
750 using namespace boost::filesystem;
752 path const dir("build/test/verify_invalid_smpte_subtitles");
753 prepare_directory (dir);
754 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
755 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
756 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
757 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
758 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
760 check_verify_result (
763 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
765 dcp::VerificationNote::Type::ERROR,
766 dcp::VerificationNote::Code::INVALID_XML,
767 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
771 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
772 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
777 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
779 path const dir("build/test/verify_empty_text_node_in_subtitles");
780 prepare_directory (dir);
781 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
782 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
783 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
784 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
786 check_verify_result (
789 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
790 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
791 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
792 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
797 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
798 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
800 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
801 prepare_directory (dir);
802 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
803 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
804 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
805 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
807 check_verify_result (
810 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
815 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
816 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
818 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
819 prepare_directory (dir);
820 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
821 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
822 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
823 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
825 check_verify_result (
828 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
829 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
834 BOOST_AUTO_TEST_CASE (verify_external_asset)
836 path const ov_dir("build/test/verify_external_asset");
837 prepare_directory (ov_dir);
839 auto image = black_image ();
840 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
841 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
842 dcp_from_frame (frame, ov_dir);
844 dcp::DCP ov (ov_dir);
847 path const vf_dir("build/test/verify_external_asset_vf");
848 prepare_directory (vf_dir);
850 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
851 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
853 check_verify_result (
856 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
862 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
864 path const dir("build/test/verify_valid_cpl_metadata");
865 prepare_directory (dir);
867 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
868 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
869 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
871 auto reel = make_shared<dcp::Reel>();
872 reel->add (reel_asset);
874 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
875 reel->add (simple_markers(16 * 24));
877 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
879 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
880 cpl->set_main_sound_sample_rate (48000);
881 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
882 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
883 cpl->set_version_number (1);
888 dcp::String::compose("libdcp %1", dcp::version),
889 dcp::String::compose("libdcp %1", dcp::version),
890 dcp::LocalTime().as_string(),
896 path find_cpl (path dir)
898 for (auto i: directory_iterator(dir)) {
899 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
904 BOOST_REQUIRE (false);
909 /* DCP with invalid CompositionMetadataAsset */
910 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
912 using namespace boost::filesystem;
914 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
915 prepare_directory (dir);
917 auto reel = make_shared<dcp::Reel>();
918 reel->add (black_picture_asset(dir));
919 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
921 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
922 cpl->set_main_sound_sample_rate (48000);
923 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
924 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
925 cpl->set_version_number (1);
927 reel->add (simple_markers());
932 dcp::String::compose("libdcp %1", dcp::version),
933 dcp::String::compose("libdcp %1", dcp::version),
934 dcp::LocalTime().as_string(),
939 Editor e (find_cpl(dir));
940 e.replace ("MainSound", "MainSoundX");
943 check_verify_result (
946 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
947 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
949 dcp::VerificationNote::Type::ERROR,
950 dcp::VerificationNote::Code::INVALID_XML,
951 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
952 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
953 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
954 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
955 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
956 "ExtensionMetadataList?,)'"),
957 canonical(cpl->file().get()),
960 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
965 /* DCP with invalid CompositionMetadataAsset */
966 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
968 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
969 prepare_directory (dir);
971 auto reel = make_shared<dcp::Reel>();
972 reel->add (black_picture_asset(dir));
973 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
975 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
976 cpl->set_main_sound_sample_rate (48000);
977 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
978 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
983 dcp::String::compose("libdcp %1", dcp::version),
984 dcp::String::compose("libdcp %1", dcp::version),
985 dcp::LocalTime().as_string(),
990 Editor e (find_cpl(dir));
991 e.replace ("meta:Width", "meta:WidthX");
994 check_verify_result (
996 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1001 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1003 path const dir("build/test/verify_invalid_language1");
1004 prepare_directory (dir);
1005 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1006 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1007 asset->_language = "wrong-andbad";
1008 asset->write (dir / "subs.mxf");
1009 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1010 reel_asset->_language = "badlang";
1011 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1013 check_verify_result (
1016 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1017 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1018 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1023 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1024 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1026 path const dir("build/test/verify_invalid_language2");
1027 prepare_directory (dir);
1028 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1029 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1030 asset->_language = "wrong-andbad";
1031 asset->write (dir / "subs.mxf");
1032 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1033 reel_asset->_language = "badlang";
1034 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1036 check_verify_result (
1039 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1040 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1041 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1046 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1047 * the release territory.
1049 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1051 path const dir("build/test/verify_invalid_language3");
1052 prepare_directory (dir);
1054 auto picture = simple_picture (dir, "foo");
1055 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1056 auto reel = make_shared<dcp::Reel>();
1057 reel->add (reel_picture);
1058 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1059 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1060 reel->add (reel_sound);
1061 reel->add (simple_markers());
1063 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1065 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1066 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1067 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1068 cpl->set_main_sound_sample_rate (48000);
1069 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1070 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1071 cpl->set_version_number (1);
1072 cpl->_release_territory = "fred-jim";
1073 auto dcp = make_shared<dcp::DCP>(dir);
1076 dcp::String::compose("libdcp %1", dcp::version),
1077 dcp::String::compose("libdcp %1", dcp::version),
1078 dcp::LocalTime().as_string(),
1082 check_verify_result (
1085 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1094 vector<dcp::VerificationNote>
1095 check_picture_size (int width, int height, int frame_rate, bool three_d)
1097 using namespace boost::filesystem;
1099 path dcp_path = "build/test/verify_picture_test";
1100 prepare_directory (dcp_path);
1102 shared_ptr<dcp::PictureAsset> mp;
1104 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1106 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1108 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1110 auto image = black_image (dcp::Size(width, height));
1111 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1112 int const length = three_d ? frame_rate * 2 : frame_rate;
1113 for (int i = 0; i < length; ++i) {
1114 picture_writer->write (j2c.data(), j2c.size());
1116 picture_writer->finalize ();
1118 auto d = make_shared<dcp::DCP>(dcp_path);
1119 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1120 cpl->set_annotation_text ("A Test DCP");
1121 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1122 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1123 cpl->set_main_sound_sample_rate (48000);
1124 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1125 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1126 cpl->set_version_number (1);
1128 auto reel = make_shared<dcp::Reel>();
1131 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1133 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1136 reel->add (simple_markers(frame_rate));
1142 dcp::String::compose("libdcp %1", dcp::version),
1143 dcp::String::compose("libdcp %1", dcp::version),
1144 dcp::LocalTime().as_string(),
1148 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1154 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1156 auto notes = check_picture_size(width, height, frame_rate, three_d);
1157 BOOST_CHECK_EQUAL (notes.size(), 0U);
1163 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1165 auto notes = check_picture_size(width, height, frame_rate, three_d);
1166 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1167 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1168 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1174 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1176 auto notes = check_picture_size(width, height, frame_rate, three_d);
1177 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1178 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1179 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1185 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1187 auto notes = check_picture_size(width, height, frame_rate, three_d);
1188 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1189 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1190 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1194 BOOST_AUTO_TEST_CASE (verify_picture_size)
1196 using namespace boost::filesystem;
1199 check_picture_size_ok (2048, 858, 24, false);
1200 check_picture_size_ok (2048, 858, 25, false);
1201 check_picture_size_ok (2048, 858, 48, false);
1202 check_picture_size_ok (2048, 858, 24, true);
1203 check_picture_size_ok (2048, 858, 25, true);
1204 check_picture_size_ok (2048, 858, 48, true);
1207 check_picture_size_ok (1998, 1080, 24, false);
1208 check_picture_size_ok (1998, 1080, 25, false);
1209 check_picture_size_ok (1998, 1080, 48, false);
1210 check_picture_size_ok (1998, 1080, 24, true);
1211 check_picture_size_ok (1998, 1080, 25, true);
1212 check_picture_size_ok (1998, 1080, 48, true);
1215 check_picture_size_ok (4096, 1716, 24, false);
1218 check_picture_size_ok (3996, 2160, 24, false);
1220 /* Bad frame size */
1221 check_picture_size_bad_frame_size (2050, 858, 24, false);
1222 check_picture_size_bad_frame_size (2048, 658, 25, false);
1223 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1224 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1226 /* Bad 2K frame rate */
1227 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1228 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1229 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1231 /* Bad 4K frame rate */
1232 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1233 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1236 auto notes = check_picture_size(3996, 2160, 24, true);
1237 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1238 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1239 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1245 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")
1248 make_shared<dcp::SubtitleString>(
1256 dcp::Time(start_frame, 24, 24),
1257 dcp::Time(end_frame, 24, 24),
1259 dcp::HAlign::CENTER,
1262 dcp::Direction::LTR,
1274 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1276 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1277 prepare_directory (dir);
1279 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1280 for (int i = 0; i < 2048; ++i) {
1281 add_test_subtitle (asset, i * 24, i * 24 + 20);
1283 asset->set_language (dcp::LanguageTag("de-DE"));
1284 asset->write (dir / "subs.mxf");
1285 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1286 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1288 check_verify_result (
1291 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1293 dcp::VerificationNote::Type::BV21_ERROR,
1294 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1296 canonical(dir / "subs.mxf")
1298 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1299 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1305 shared_ptr<dcp::SMPTESubtitleAsset>
1306 make_large_subtitle_asset (path font_file)
1308 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1309 dcp::ArrayData big_fake_font(1024 * 1024);
1310 big_fake_font.write (font_file);
1311 for (int i = 0; i < 116; ++i) {
1312 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1320 verify_timed_text_asset_too_large (string name)
1322 auto const dir = path("build/test") / name;
1323 prepare_directory (dir);
1324 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1325 add_test_subtitle (asset, 0, 240);
1326 asset->set_language (dcp::LanguageTag("de-DE"));
1327 asset->write (dir / "subs.mxf");
1329 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1330 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1332 check_verify_result (
1335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1338 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1339 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1344 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1346 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1347 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1351 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1353 path dir = "build/test/verify_missing_subtitle_language";
1354 prepare_directory (dir);
1355 auto dcp = make_simple (dir, 1, 106);
1358 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1359 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1360 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1361 "<ContentTitleText>Content</ContentTitleText>"
1362 "<AnnotationText>Annotation</AnnotationText>"
1363 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1364 "<ReelNumber>1</ReelNumber>"
1365 "<EditRate>24 1</EditRate>"
1366 "<TimeCodeRate>24</TimeCodeRate>"
1367 "<StartTime>00:00:00:00</StartTime>"
1368 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1370 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1371 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1372 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1378 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1379 BOOST_REQUIRE (xml_file);
1380 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1382 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1383 subs->write (dir / "subs.mxf");
1385 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1386 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1388 dcp::String::compose("libdcp %1", dcp::version),
1389 dcp::String::compose("libdcp %1", dcp::version),
1390 dcp::LocalTime().as_string(),
1394 check_verify_result (
1397 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1398 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1403 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1405 path path ("build/test/verify_mismatched_subtitle_languages");
1406 auto constexpr reel_length = 192;
1407 auto dcp = make_simple (path, 2, reel_length);
1408 auto cpl = dcp->cpls()[0];
1411 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1412 subs->set_language (dcp::LanguageTag("de-DE"));
1413 subs->add (simple_subtitle());
1414 subs->write (path / "subs1.mxf");
1415 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1416 cpl->reels()[0]->add(reel_subs);
1420 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1421 subs->set_language (dcp::LanguageTag("en-US"));
1422 subs->add (simple_subtitle());
1423 subs->write (path / "subs2.mxf");
1424 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1425 cpl->reels()[1]->add(reel_subs);
1429 dcp::String::compose("libdcp %1", dcp::version),
1430 dcp::String::compose("libdcp %1", dcp::version),
1431 dcp::LocalTime().as_string(),
1435 check_verify_result (
1438 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1439 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1440 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1445 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1447 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1448 auto constexpr reel_length = 192;
1449 auto dcp = make_simple (path, 2, reel_length);
1450 auto cpl = dcp->cpls()[0];
1453 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1454 ccaps->set_language (dcp::LanguageTag("de-DE"));
1455 ccaps->add (simple_subtitle());
1456 ccaps->write (path / "subs1.mxf");
1457 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1458 cpl->reels()[0]->add(reel_ccaps);
1462 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1463 ccaps->set_language (dcp::LanguageTag("en-US"));
1464 ccaps->add (simple_subtitle());
1465 ccaps->write (path / "subs2.mxf");
1466 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1467 cpl->reels()[1]->add(reel_ccaps);
1471 dcp::String::compose("libdcp %1", dcp::version),
1472 dcp::String::compose("libdcp %1", dcp::version),
1473 dcp::LocalTime().as_string(),
1477 check_verify_result (
1480 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1486 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1488 path dir = "build/test/verify_missing_subtitle_start_time";
1489 prepare_directory (dir);
1490 auto dcp = make_simple (dir, 1, 106);
1493 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1494 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1495 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1496 "<ContentTitleText>Content</ContentTitleText>"
1497 "<AnnotationText>Annotation</AnnotationText>"
1498 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1499 "<ReelNumber>1</ReelNumber>"
1500 "<Language>de-DE</Language>"
1501 "<EditRate>24 1</EditRate>"
1502 "<TimeCodeRate>24</TimeCodeRate>"
1503 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1505 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1506 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1507 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1513 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1514 BOOST_REQUIRE (xml_file);
1515 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1517 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1518 subs->write (dir / "subs.mxf");
1520 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1521 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1523 dcp::String::compose("libdcp %1", dcp::version),
1524 dcp::String::compose("libdcp %1", dcp::version),
1525 dcp::LocalTime().as_string(),
1529 check_verify_result (
1532 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1533 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1538 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1540 path dir = "build/test/verify_invalid_subtitle_start_time";
1541 prepare_directory (dir);
1542 auto dcp = make_simple (dir, 1, 106);
1545 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1546 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1547 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1548 "<ContentTitleText>Content</ContentTitleText>"
1549 "<AnnotationText>Annotation</AnnotationText>"
1550 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1551 "<ReelNumber>1</ReelNumber>"
1552 "<Language>de-DE</Language>"
1553 "<EditRate>24 1</EditRate>"
1554 "<TimeCodeRate>24</TimeCodeRate>"
1555 "<StartTime>00:00:02:00</StartTime>"
1556 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1558 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1559 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1560 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1566 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1567 BOOST_REQUIRE (xml_file);
1568 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1570 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1571 subs->write (dir / "subs.mxf");
1573 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1574 dcp->cpls().front()->reels().front()->add(reel_subs);
1576 dcp::String::compose("libdcp %1", dcp::version),
1577 dcp::String::compose("libdcp %1", dcp::version),
1578 dcp::LocalTime().as_string(),
1582 check_verify_result (
1585 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1586 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1594 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1597 , v_position(v_position_)
1605 dcp::VAlign v_align;
1611 shared_ptr<dcp::CPL>
1612 dcp_with_text (path dir, vector<TestText> subs)
1614 prepare_directory (dir);
1615 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1616 asset->set_start_time (dcp::Time());
1617 for (auto i: subs) {
1618 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1620 asset->set_language (dcp::LanguageTag("de-DE"));
1621 asset->write (dir / "subs.mxf");
1623 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1624 return write_dcp_with_single_asset (dir, reel_asset);
1629 shared_ptr<dcp::CPL>
1630 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1632 prepare_directory (dir);
1633 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1634 asset->set_start_time (dcp::Time());
1635 asset->set_language (dcp::LanguageTag("de-DE"));
1637 auto subs_mxf = dir / "subs.mxf";
1638 asset->write (subs_mxf);
1640 /* The call to write() puts the asset into the DCP correctly but it will have
1641 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1644 ASDCP::TimedText::MXFWriter writer;
1645 ASDCP::WriterInfo writer_info;
1646 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1648 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1649 DCP_ASSERT (c == Kumu::UUID_Length);
1650 ASDCP::TimedText::TimedTextDescriptor descriptor;
1651 descriptor.ContainerDuration = asset->intrinsic_duration();
1652 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1653 DCP_ASSERT (c == Kumu::UUID_Length);
1654 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1655 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1656 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1657 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1660 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1661 return write_dcp_with_single_asset (dir, reel_asset);
1665 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1667 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1668 /* Just too early */
1669 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1670 check_verify_result (
1673 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1680 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1682 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1683 /* Just late enough */
1684 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1685 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1689 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1691 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1692 prepare_directory (dir);
1694 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1695 asset1->set_start_time (dcp::Time());
1696 /* Just late enough */
1697 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1698 asset1->set_language (dcp::LanguageTag("de-DE"));
1699 asset1->write (dir / "subs1.mxf");
1700 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1701 auto reel1 = make_shared<dcp::Reel>();
1702 reel1->add (reel_asset1);
1703 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1704 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1705 reel1->add (markers1);
1707 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1708 asset2->set_start_time (dcp::Time());
1709 /* This would be too early on first reel but should be OK on the second */
1710 add_test_subtitle (asset2, 3, 4 * 24);
1711 asset2->set_language (dcp::LanguageTag("de-DE"));
1712 asset2->write (dir / "subs2.mxf");
1713 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1714 auto reel2 = make_shared<dcp::Reel>();
1715 reel2->add (reel_asset2);
1716 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1717 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1718 reel2->add (markers2);
1720 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1723 auto dcp = make_shared<dcp::DCP>(dir);
1726 dcp::String::compose("libdcp %1", dcp::version),
1727 dcp::String::compose("libdcp %1", dcp::version),
1728 dcp::LocalTime().as_string(),
1733 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1737 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1739 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1740 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1744 { 5 * 24 + 1, 6 * 24 },
1746 check_verify_result (
1749 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1755 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1757 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1758 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1762 { 5 * 24 + 16, 8 * 24 },
1764 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1768 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1770 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1771 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1772 check_verify_result (
1775 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1781 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1783 auto const dir = path("build/test/verify_valid_subtitle_duration");
1784 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1785 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1789 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1791 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1792 prepare_directory (dir);
1793 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1794 asset->set_start_time (dcp::Time());
1795 add_test_subtitle (asset, 0, 4 * 24);
1796 asset->set_language (dcp::LanguageTag("de-DE"));
1797 asset->write (dir / "subs.mxf");
1799 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1800 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1801 check_verify_result (
1804 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1805 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1806 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1807 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1813 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1815 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1816 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1819 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1820 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1821 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1822 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1824 check_verify_result (
1827 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1828 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1833 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1835 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1836 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1839 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1840 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1841 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1843 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1847 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1849 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1850 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1853 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1854 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1855 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1856 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1858 check_verify_result (
1861 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1862 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1867 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1869 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1870 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1873 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1874 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1875 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1876 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1878 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1882 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1884 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1885 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1888 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1890 check_verify_result (
1893 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1899 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1901 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1902 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1905 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1907 check_verify_result (
1910 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1916 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1918 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1919 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1922 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1923 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1924 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1925 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1927 check_verify_result (
1930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1936 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1938 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1939 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1942 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1943 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1944 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1946 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1950 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1952 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1953 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1956 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1957 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1958 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1959 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1961 check_verify_result (
1964 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1965 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1970 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1972 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1973 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1976 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1977 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1978 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1979 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1981 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1985 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1987 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1988 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1991 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1993 check_verify_result (
1996 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2001 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2003 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2004 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2007 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2009 check_verify_result (
2012 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2013 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2018 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2020 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2021 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2024 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2025 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2026 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2028 check_verify_result (
2031 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2036 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2038 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2039 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2042 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2043 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2044 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2046 check_verify_result (
2049 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2050 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2055 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2057 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
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::TOP, "fine" },
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2073 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2075 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2076 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2079 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2080 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2081 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2083 check_verify_result (
2086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2091 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2093 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2094 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2095 check_verify_result (
2098 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2099 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2104 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2106 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2107 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2108 check_verify_result (
2111 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2117 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2119 path const dir("build/test/verify_invalid_sound_frame_rate");
2120 prepare_directory (dir);
2122 auto picture = simple_picture (dir, "foo");
2123 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2124 auto reel = make_shared<dcp::Reel>();
2125 reel->add (reel_picture);
2126 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2127 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2128 reel->add (reel_sound);
2129 reel->add (simple_markers());
2130 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2132 auto dcp = make_shared<dcp::DCP>(dir);
2135 dcp::String::compose("libdcp %1", dcp::version),
2136 dcp::String::compose("libdcp %1", dcp::version),
2137 dcp::LocalTime().as_string(),
2141 check_verify_result (
2144 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2145 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2150 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2152 path const dir("build/test/verify_missing_cpl_annotation_text");
2153 auto dcp = make_simple (dir);
2155 dcp::String::compose("libdcp %1", dcp::version),
2156 dcp::String::compose("libdcp %1", dcp::version),
2157 dcp::LocalTime().as_string(),
2161 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2163 auto const cpl = dcp->cpls()[0];
2166 BOOST_REQUIRE (cpl->file());
2167 Editor e(cpl->file().get());
2168 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2171 check_verify_result (
2174 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2175 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2180 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2182 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2183 auto dcp = make_simple (dir);
2185 dcp::String::compose("libdcp %1", dcp::version),
2186 dcp::String::compose("libdcp %1", dcp::version),
2187 dcp::LocalTime().as_string(),
2191 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2192 auto const cpl = dcp->cpls()[0];
2195 BOOST_REQUIRE (cpl->file());
2196 Editor e(cpl->file().get());
2197 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2200 check_verify_result (
2203 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2204 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2209 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2211 path const dir("build/test/verify_mismatched_asset_duration");
2212 prepare_directory (dir);
2213 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2214 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2216 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2217 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2219 auto reel = make_shared<dcp::Reel>(
2220 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2221 make_shared<dcp::ReelSoundAsset>(ms, 0)
2224 reel->add (simple_markers());
2229 dcp::String::compose("libdcp %1", dcp::version),
2230 dcp::String::compose("libdcp %1", dcp::version),
2231 dcp::LocalTime().as_string(),
2235 check_verify_result (
2238 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2239 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2246 shared_ptr<dcp::CPL>
2247 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2249 prepare_directory (dir);
2250 auto dcp = make_shared<dcp::DCP>(dir);
2251 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2253 auto constexpr reel_length = 192;
2255 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2256 subs->set_language (dcp::LanguageTag("de-DE"));
2257 subs->set_start_time (dcp::Time());
2258 subs->add (simple_subtitle());
2259 subs->write (dir / "subs.mxf");
2260 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2262 auto reel1 = make_shared<dcp::Reel>(
2263 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2264 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2268 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2271 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2272 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2273 reel1->add (markers1);
2277 auto reel2 = make_shared<dcp::Reel>(
2278 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2279 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2283 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2286 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2287 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2288 reel2->add (markers2);
2294 dcp::String::compose("libdcp %1", dcp::version),
2295 dcp::String::compose("libdcp %1", dcp::version),
2296 dcp::LocalTime().as_string(),
2304 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2307 path dir ("build/test/missing_main_subtitle_from_some_reels");
2308 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2309 check_verify_result (
2312 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2313 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2319 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2320 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2321 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2325 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2326 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2327 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2333 shared_ptr<dcp::CPL>
2334 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2336 prepare_directory (dir);
2337 auto dcp = make_shared<dcp::DCP>(dir);
2338 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2340 auto constexpr reel_length = 192;
2342 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2343 subs->set_language (dcp::LanguageTag("de-DE"));
2344 subs->set_start_time (dcp::Time());
2345 subs->add (simple_subtitle());
2346 subs->write (dir / "subs.mxf");
2348 auto reel1 = make_shared<dcp::Reel>(
2349 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2350 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2353 for (int i = 0; i < caps_in_reel1; ++i) {
2354 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2357 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2358 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2359 reel1->add (markers1);
2363 auto reel2 = make_shared<dcp::Reel>(
2364 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2365 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2368 for (int i = 0; i < caps_in_reel2; ++i) {
2369 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2372 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2373 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2374 reel2->add (markers2);
2380 dcp::String::compose("libdcp %1", dcp::version),
2381 dcp::String::compose("libdcp %1", dcp::version),
2382 dcp::LocalTime().as_string(),
2390 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2393 path dir ("build/test/mismatched_closed_caption_asset_counts");
2394 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2395 check_verify_result (
2398 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2399 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2404 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2405 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2406 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2410 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2411 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2412 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2419 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2421 prepare_directory (dir);
2422 auto dcp = make_shared<dcp::DCP>(dir);
2423 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2425 auto constexpr reel_length = 192;
2427 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2428 subs->set_language (dcp::LanguageTag("de-DE"));
2429 subs->set_start_time (dcp::Time());
2430 subs->add (simple_subtitle());
2431 subs->write (dir / "subs.mxf");
2432 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2435 auto reel = make_shared<dcp::Reel>(
2436 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2437 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2440 reel->add (reel_text);
2442 reel->add (simple_markers(reel_length));
2448 dcp::String::compose("libdcp %1", dcp::version),
2449 dcp::String::compose("libdcp %1", dcp::version),
2450 dcp::LocalTime().as_string(),
2454 check_verify_result (
2457 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2458 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2463 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2465 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2466 "build/test/verify_subtitle_entry_point_must_be_present",
2467 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2468 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2469 asset->unset_entry_point ();
2473 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2474 "build/test/verify_subtitle_entry_point_must_be_zero",
2475 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2476 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2477 asset->set_entry_point (4);
2481 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2482 "build/test/verify_closed_caption_entry_point_must_be_present",
2483 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2484 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2485 asset->unset_entry_point ();
2489 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2490 "build/test/verify_closed_caption_entry_point_must_be_zero",
2491 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2492 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2493 asset->set_entry_point (9);
2499 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2503 path const dir("build/test/verify_missing_hash");
2504 auto dcp = make_simple (dir);
2506 dcp::String::compose("libdcp %1", dcp::version),
2507 dcp::String::compose("libdcp %1", dcp::version),
2508 dcp::LocalTime().as_string(),
2512 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2513 auto const cpl = dcp->cpls()[0];
2514 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2515 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2516 auto asset_id = cpl->reels()[0]->main_picture()->id();
2519 BOOST_REQUIRE (cpl->file());
2520 Editor e(cpl->file().get());
2521 e.delete_first_line_containing("<Hash>");
2524 check_verify_result (
2527 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2528 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2535 verify_markers_test (
2537 vector<pair<dcp::Marker, dcp::Time>> markers,
2538 vector<dcp::VerificationNote> test_notes
2541 auto dcp = make_simple (dir);
2542 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2543 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2544 for (auto const& i: markers) {
2545 markers_asset->set (i.first, i.second);
2547 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2549 dcp::String::compose("libdcp %1", dcp::version),
2550 dcp::String::compose("libdcp %1", dcp::version),
2551 dcp::LocalTime().as_string(),
2555 check_verify_result ({dir}, test_notes);
2559 BOOST_AUTO_TEST_CASE (verify_markers)
2561 verify_markers_test (
2562 "build/test/verify_markers_all_correct",
2564 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2565 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2566 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2567 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2572 verify_markers_test (
2573 "build/test/verify_markers_missing_ffec",
2575 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2576 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2577 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2580 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2583 verify_markers_test (
2584 "build/test/verify_markers_missing_ffmc",
2586 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2587 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2588 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2591 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2594 verify_markers_test (
2595 "build/test/verify_markers_missing_ffoc",
2597 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2598 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2599 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2602 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2605 verify_markers_test (
2606 "build/test/verify_markers_missing_lfoc",
2608 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2609 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2610 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2613 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2616 verify_markers_test (
2617 "build/test/verify_markers_incorrect_ffoc",
2619 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2620 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2621 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2622 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2625 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2628 verify_markers_test (
2629 "build/test/verify_markers_incorrect_lfoc",
2631 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2632 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2633 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2634 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2642 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2644 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2645 prepare_directory (dir);
2646 auto dcp = make_simple (dir);
2647 auto cpl = dcp->cpls()[0];
2648 cpl->unset_version_number();
2650 dcp::String::compose("libdcp %1", dcp::version),
2651 dcp::String::compose("libdcp %1", dcp::version),
2652 dcp::LocalTime().as_string(),
2656 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2660 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2662 path dir = "build/test/verify_missing_extension_metadata1";
2663 auto dcp = make_simple (dir);
2665 dcp::String::compose("libdcp %1", dcp::version),
2666 dcp::String::compose("libdcp %1", dcp::version),
2667 dcp::LocalTime().as_string(),
2671 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2672 auto cpl = dcp->cpls()[0];
2675 Editor e (cpl->file().get());
2676 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2679 check_verify_result (
2682 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2683 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2688 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2690 path dir = "build/test/verify_missing_extension_metadata2";
2691 auto dcp = make_simple (dir);
2693 dcp::String::compose("libdcp %1", dcp::version),
2694 dcp::String::compose("libdcp %1", dcp::version),
2695 dcp::LocalTime().as_string(),
2699 auto cpl = dcp->cpls()[0];
2702 Editor e (cpl->file().get());
2703 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2706 check_verify_result (
2709 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2710 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2715 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2717 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2718 auto dcp = make_simple (dir);
2720 dcp::String::compose("libdcp %1", dcp::version),
2721 dcp::String::compose("libdcp %1", dcp::version),
2722 dcp::LocalTime().as_string(),
2726 auto const cpl = dcp->cpls()[0];
2729 Editor e (cpl->file().get());
2730 e.replace ("<meta:Name>A", "<meta:NameX>A");
2731 e.replace ("n</meta:Name>", "n</meta:NameX>");
2734 check_verify_result (
2737 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2738 { 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 },
2739 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2744 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2746 path dir = "build/test/verify_invalid_extension_metadata1";
2747 auto dcp = make_simple (dir);
2749 dcp::String::compose("libdcp %1", dcp::version),
2750 dcp::String::compose("libdcp %1", dcp::version),
2751 dcp::LocalTime().as_string(),
2755 auto cpl = dcp->cpls()[0];
2758 Editor e (cpl->file().get());
2759 e.replace ("Application", "Fred");
2762 check_verify_result (
2765 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2766 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2771 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2773 path dir = "build/test/verify_invalid_extension_metadata2";
2774 auto dcp = make_simple (dir);
2776 dcp::String::compose("libdcp %1", dcp::version),
2777 dcp::String::compose("libdcp %1", dcp::version),
2778 dcp::LocalTime().as_string(),
2782 auto cpl = dcp->cpls()[0];
2785 Editor e (cpl->file().get());
2786 e.replace ("DCP Constraints Profile", "Fred");
2789 check_verify_result (
2792 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2798 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2800 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2801 auto dcp = make_simple (dir);
2803 dcp::String::compose("libdcp %1", dcp::version),
2804 dcp::String::compose("libdcp %1", dcp::version),
2805 dcp::LocalTime().as_string(),
2809 auto const cpl = dcp->cpls()[0];
2812 Editor e (cpl->file().get());
2813 e.replace ("<meta:Value>", "<meta:ValueX>");
2814 e.replace ("</meta:Value>", "</meta:ValueX>");
2817 check_verify_result (
2820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2821 { 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 },
2822 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2827 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2829 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2830 auto dcp = make_simple (dir);
2832 dcp::String::compose("libdcp %1", dcp::version),
2833 dcp::String::compose("libdcp %1", dcp::version),
2834 dcp::LocalTime().as_string(),
2838 auto const cpl = dcp->cpls()[0];
2841 Editor e (cpl->file().get());
2842 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2845 check_verify_result (
2848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2849 { 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() },
2854 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2856 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2857 auto dcp = make_simple (dir);
2859 dcp::String::compose("libdcp %1", dcp::version),
2860 dcp::String::compose("libdcp %1", dcp::version),
2861 dcp::LocalTime().as_string(),
2865 auto const cpl = dcp->cpls()[0];
2868 Editor e (cpl->file().get());
2869 e.replace ("<meta:Property>", "<meta:PropertyX>");
2870 e.replace ("</meta:Property>", "</meta:PropertyX>");
2873 check_verify_result (
2876 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2877 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2878 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2883 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2885 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2886 auto dcp = make_simple (dir);
2888 dcp::String::compose("libdcp %1", dcp::version),
2889 dcp::String::compose("libdcp %1", dcp::version),
2890 dcp::LocalTime().as_string(),
2894 auto const cpl = dcp->cpls()[0];
2897 Editor e (cpl->file().get());
2898 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2899 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2902 check_verify_result (
2905 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2906 { 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 },
2907 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2913 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2915 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2916 prepare_directory (dir);
2917 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2918 copy_file (i.path(), dir / i.path().filename());
2921 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2922 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2926 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2929 check_verify_result (
2932 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2933 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2934 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2936 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2937 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2944 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2946 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2947 prepare_directory (dir);
2948 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2949 copy_file (i.path(), dir / i.path().filename());
2952 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2953 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2956 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2959 check_verify_result (
2962 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2963 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2964 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2965 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2966 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2967 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2973 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2975 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2976 prepare_directory (dir);
2977 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2978 copy_file (i.path(), dir / i.path().filename());
2982 Editor e (dir / dcp_test1_pkl);
2983 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2986 check_verify_result ({dir}, {});
2990 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2992 path dir ("build/test/verify_must_not_be_partially_encrypted");
2993 prepare_directory (dir);
2997 auto signer = make_shared<dcp::CertificateChain>();
2998 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2999 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
3000 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
3001 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
3003 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3007 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3010 auto writer = mp->start_write (dir / "video.mxf", false);
3011 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3012 for (int i = 0; i < 24; ++i) {
3013 writer->write (j2c.data(), j2c.size());
3015 writer->finalize ();
3017 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3019 auto reel = make_shared<dcp::Reel>(
3020 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3021 make_shared<dcp::ReelSoundAsset>(ms, 0)
3024 reel->add (simple_markers());
3028 cpl->set_content_version (
3029 {"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"}
3031 cpl->set_annotation_text ("A Test DCP");
3032 cpl->set_issuer ("OpenDCP 0.0.25");
3033 cpl->set_creator ("OpenDCP 0.0.25");
3034 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3035 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3036 cpl->set_main_sound_sample_rate (48000);
3037 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3038 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3039 cpl->set_version_number (1);
3043 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
3045 check_verify_result (
3048 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3053 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3055 vector<dcp::VerificationNote> notes;
3056 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"));
3057 auto reader = picture.start_read ();
3058 auto frame = reader->get_frame (0);
3059 verify_j2k (frame, notes);
3060 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3064 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3066 vector<dcp::VerificationNote> notes;
3067 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3068 auto reader = picture.start_read ();
3069 auto frame = reader->get_frame (0);
3070 verify_j2k (frame, notes);
3071 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3075 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3077 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3078 prepare_directory (dir);
3079 auto dcp = make_simple (dir);
3081 vector<dcp::VerificationNote> notes;
3082 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3083 auto reader = picture.start_read ();
3084 auto frame = reader->get_frame (0);
3085 verify_j2k (frame, notes);
3086 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3090 /** Check that ResourceID and the XML ID being different is spotted */
3091 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3093 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3094 prepare_directory (dir);
3096 ASDCP::WriterInfo writer_info;
3097 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3100 auto mxf_id = dcp::make_uuid ();
3101 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3102 BOOST_REQUIRE (c == Kumu::UUID_Length);
3104 auto resource_id = dcp::make_uuid ();
3105 ASDCP::TimedText::TimedTextDescriptor descriptor;
3106 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3107 DCP_ASSERT (c == Kumu::UUID_Length);
3109 auto xml_id = dcp::make_uuid ();
3110 ASDCP::TimedText::MXFWriter writer;
3111 auto subs_mxf = dir / "subs.mxf";
3112 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3113 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3114 writer.WriteTimedTextResource (dcp::String::compose(
3115 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3116 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3117 "<Id>urn:uuid:%1</Id>"
3118 "<ContentTitleText>Content</ContentTitleText>"
3119 "<AnnotationText>Annotation</AnnotationText>"
3120 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3121 "<ReelNumber>1</ReelNumber>"
3122 "<Language>en-US</Language>"
3123 "<EditRate>25 1</EditRate>"
3124 "<TimeCodeRate>25</TimeCodeRate>"
3125 "<StartTime>00:00:00:00</StartTime>"
3127 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3128 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3129 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3138 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3139 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3141 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3143 check_verify_result (
3146 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3147 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3148 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3149 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3154 /** Check that ResourceID and the MXF ID being the same is spotted */
3155 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3157 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3158 prepare_directory (dir);
3160 ASDCP::WriterInfo writer_info;
3161 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3164 auto mxf_id = dcp::make_uuid ();
3165 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3166 BOOST_REQUIRE (c == Kumu::UUID_Length);
3168 auto resource_id = mxf_id;
3169 ASDCP::TimedText::TimedTextDescriptor descriptor;
3170 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3171 DCP_ASSERT (c == Kumu::UUID_Length);
3173 auto xml_id = resource_id;
3174 ASDCP::TimedText::MXFWriter writer;
3175 auto subs_mxf = dir / "subs.mxf";
3176 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3177 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3178 writer.WriteTimedTextResource (dcp::String::compose(
3179 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3180 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3181 "<Id>urn:uuid:%1</Id>"
3182 "<ContentTitleText>Content</ContentTitleText>"
3183 "<AnnotationText>Annotation</AnnotationText>"
3184 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3185 "<ReelNumber>1</ReelNumber>"
3186 "<Language>en-US</Language>"
3187 "<EditRate>25 1</EditRate>"
3188 "<TimeCodeRate>25</TimeCodeRate>"
3189 "<StartTime>00:00:00:00</StartTime>"
3191 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3192 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3193 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3202 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3203 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3205 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3207 check_verify_result (
3210 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3211 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3212 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3213 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3218 /** Check a DCP with a 3D asset marked as 2D */
3219 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3221 check_verify_result (
3222 { private_test / "data" / "xm" },
3225 dcp::VerificationNote::Type::WARNING,
3226 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3229 dcp::VerificationNote::Type::BV21_ERROR,
3230 dcp::VerificationNote::Code::INVALID_STANDARD