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);
119 setup (int reference_number, string verify_test_suffix)
121 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
122 prepare_directory (dir);
123 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
124 copy_file (i.path(), dir / i.path().filename());
133 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
135 auto reel = make_shared<dcp::Reel>();
136 reel->add (reel_asset);
137 reel->add (simple_markers());
139 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
141 auto dcp = make_shared<dcp::DCP>(dir);
144 dcp::String::compose("libdcp %1", dcp::version),
145 dcp::String::compose("libdcp %1", dcp::version),
146 dcp::LocalTime().as_string(),
154 /** Class that can alter a file by searching and replacing strings within it.
155 * On destruction modifies the file whose name was given to the constructor.
163 _content = dcp::file_to_string (_path);
168 auto f = fopen(_path.string().c_str(), "w");
170 fwrite (_content.c_str(), _content.length(), 1, f);
174 void replace (string a, string b)
176 auto old_content = _content;
177 boost::algorithm::replace_all (_content, a, b);
178 BOOST_REQUIRE (_content != old_content);
181 void delete_first_line_containing (string s)
183 vector<string> lines;
184 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
185 auto old_content = _content;
188 for (auto i: lines) {
189 if (i.find(s) == string::npos || done) {
190 _content += i + "\n";
195 BOOST_REQUIRE (_content != old_content);
198 void delete_lines (string from, string to)
200 vector<string> lines;
201 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
202 bool deleting = false;
203 auto old_content = _content;
205 for (auto i: lines) {
206 if (i.find(from) != string::npos) {
210 _content += i + "\n";
212 if (deleting && i.find(to) != string::npos) {
216 BOOST_REQUIRE (_content != old_content);
221 std::string _content;
225 LIBDCP_DISABLE_WARNINGS
228 dump_notes (vector<dcp::VerificationNote> const & notes)
230 for (auto i: notes) {
231 std::cout << dcp::note_to_string(i) << "\n";
234 LIBDCP_ENABLE_WARNINGS
239 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
241 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
242 std::sort (notes.begin(), notes.end());
243 std::sort (test_notes.begin(), test_notes.end());
245 string message = "\nVerification notes from test:\n";
246 for (auto i: notes) {
247 message += " " + note_to_string(i) + "\n";
249 message += "Expected:\n";
250 for (auto i: test_notes) {
251 message += " " + note_to_string(i) + "\n";
254 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
260 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
262 auto dir = setup (1, suffix);
265 Editor e (file(suffix));
266 e.replace (from, to);
269 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
271 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
272 auto i = notes.begin();
273 auto j = codes.begin();
274 while (i != notes.end()) {
275 BOOST_CHECK_EQUAL (i->code(), *j);
282 BOOST_AUTO_TEST_CASE (verify_no_error)
285 auto dir = setup (1, "no_error");
286 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
288 path const cpl_file = dir / dcp_test1_cpl;
289 path const pkl_file = dir / dcp_test1_pkl;
290 path const assetmap_file = dir / "ASSETMAP.xml";
292 auto st = stages.begin();
293 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
294 BOOST_REQUIRE (st->second);
295 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
297 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
298 BOOST_REQUIRE (st->second);
299 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
301 BOOST_CHECK_EQUAL (st->first, "Checking reel");
302 BOOST_REQUIRE (!st->second);
304 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
305 BOOST_REQUIRE (st->second);
306 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
308 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
309 BOOST_REQUIRE (st->second);
310 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
312 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
313 BOOST_REQUIRE (st->second);
314 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
316 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
317 BOOST_REQUIRE (st->second);
318 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
320 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
321 BOOST_REQUIRE (st->second);
322 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
324 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
325 BOOST_REQUIRE (st->second);
326 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
328 BOOST_REQUIRE (st == stages.end());
330 BOOST_CHECK_EQUAL (notes.size(), 0);
334 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
336 using namespace boost::filesystem;
338 auto dir = setup (1, "incorrect_picture_sound_hash");
340 auto video_path = path(dir / "video.mxf");
341 auto mod = fopen(video_path.string().c_str(), "r+b");
343 fseek (mod, 4096, SEEK_SET);
345 fwrite (&x, sizeof(x), 1, mod);
348 auto audio_path = path(dir / "audio.mxf");
349 mod = fopen(audio_path.string().c_str(), "r+b");
351 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
352 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
355 dcp::ASDCPErrorSuspender sus;
356 check_verify_result (
359 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
360 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
365 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
367 using namespace boost::filesystem;
369 auto dir = setup (1, "mismatched_picture_sound_hashes");
372 Editor e (dir / dcp_test1_pkl);
373 e.replace ("<Hash>", "<Hash>x");
376 check_verify_result (
379 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
380 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
381 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
382 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xLq7ot/GobgrqUYdlbR8FCD5APqs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
383 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xgVKhC9IkWyzQbgzpFcJ1bpqbtwk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
384 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xc1DRq6GaSzV2brF0YnSNed46nqk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 }
389 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
391 auto dir = setup (1, "failed_read_content_kind");
394 Editor e (dir / dcp_test1_cpl);
395 e.replace ("<ContentKind>", "<ContentKind>x");
398 check_verify_result (
400 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
409 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
417 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
423 asset_map (string suffix)
425 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
429 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
431 check_verify_result_after_replace (
432 "invalid_picture_frame_rate", &cpl,
433 "<FrameRate>24 1", "<FrameRate>99 1",
434 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
435 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
439 BOOST_AUTO_TEST_CASE (verify_missing_asset)
441 auto dir = setup (1, "missing_asset");
442 remove (dir / "video.mxf");
443 check_verify_result (
446 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
451 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
453 check_verify_result_after_replace (
454 "empty_asset_path", &asset_map,
455 "<Path>video.mxf</Path>", "<Path></Path>",
456 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
461 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
463 check_verify_result_after_replace (
464 "mismatched_standard", &cpl,
465 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
466 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
467 dcp::VerificationNote::Code::INVALID_XML,
468 dcp::VerificationNote::Code::INVALID_XML,
469 dcp::VerificationNote::Code::INVALID_XML,
470 dcp::VerificationNote::Code::INVALID_XML,
471 dcp::VerificationNote::Code::INVALID_XML,
472 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
477 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
479 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
480 check_verify_result_after_replace (
481 "invalid_xml_cpl_id", &cpl,
482 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
483 { dcp::VerificationNote::Code::INVALID_XML }
488 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
490 check_verify_result_after_replace (
491 "invalid_xml_issue_date", &cpl,
492 "<IssueDate>", "<IssueDate>x",
493 { dcp::VerificationNote::Code::INVALID_XML,
494 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
499 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
501 check_verify_result_after_replace (
502 "invalid_xml_pkl_id", &pkl,
503 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
504 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
505 { dcp::VerificationNote::Code::INVALID_XML }
510 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
512 check_verify_result_after_replace (
513 "invalix_xml_asset_map_id", &asset_map,
514 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
515 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
516 { dcp::VerificationNote::Code::INVALID_XML }
521 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
524 auto dir = setup (3, "verify_invalid_standard");
525 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
527 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
528 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
529 path const assetmap_file = dir / "ASSETMAP";
531 auto st = stages.begin();
532 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
533 BOOST_REQUIRE (st->second);
534 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
536 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
537 BOOST_REQUIRE (st->second);
538 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
540 BOOST_CHECK_EQUAL (st->first, "Checking reel");
541 BOOST_REQUIRE (!st->second);
543 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
544 BOOST_REQUIRE (st->second);
545 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
547 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
548 BOOST_REQUIRE (st->second);
549 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
551 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
552 BOOST_REQUIRE (st->second);
553 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
555 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
556 BOOST_REQUIRE (st->second);
557 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
559 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
560 BOOST_REQUIRE (st->second);
561 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
563 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
564 BOOST_REQUIRE (st->second);
565 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
567 BOOST_REQUIRE (st == stages.end());
569 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
570 auto i = notes.begin ();
571 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
572 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
574 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
575 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
578 /* DCP with a short asset */
579 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
581 auto dir = setup (8, "invalid_duration");
582 check_verify_result (
585 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
587 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
588 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
589 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
590 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
597 dcp_from_frame (dcp::ArrayData const& frame, path dir)
599 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
600 create_directories (dir);
601 auto writer = asset->start_write (dir / "pic.mxf", true);
602 for (int i = 0; i < 24; ++i) {
603 writer->write (frame.data(), frame.size());
607 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
608 return write_dcp_with_single_asset (dir, reel_asset);
612 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
614 int const too_big = 1302083 * 2;
616 /* Compress a black image */
617 auto image = black_image ();
618 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
619 BOOST_REQUIRE (frame.size() < too_big);
621 /* Place it in a bigger block with some zero padding at the end */
622 dcp::ArrayData oversized_frame(too_big);
623 memcpy (oversized_frame.data(), frame.data(), frame.size());
624 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
626 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
627 prepare_directory (dir);
628 auto cpl = dcp_from_frame (oversized_frame, dir);
630 check_verify_result (
633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
634 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
635 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
640 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
642 int const nearly_too_big = 1302083 * 0.98;
644 /* Compress a black image */
645 auto image = black_image ();
646 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
647 BOOST_REQUIRE (frame.size() < nearly_too_big);
649 /* Place it in a bigger block with some zero padding at the end */
650 dcp::ArrayData oversized_frame(nearly_too_big);
651 memcpy (oversized_frame.data(), frame.data(), frame.size());
652 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
654 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
655 prepare_directory (dir);
656 auto cpl = dcp_from_frame (oversized_frame, dir);
658 check_verify_result (
661 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
662 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
663 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
668 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
670 /* Compress a black image */
671 auto image = black_image ();
672 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
673 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
675 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
676 prepare_directory (dir);
677 auto cpl = dcp_from_frame (frame, dir);
679 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
683 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
685 path const dir("build/test/verify_valid_interop_subtitles");
686 prepare_directory (dir);
687 copy_file ("test/data/subs1.xml", dir / "subs.xml");
688 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
689 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
690 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
692 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
696 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
698 using namespace boost::filesystem;
700 path const dir("build/test/verify_invalid_interop_subtitles");
701 prepare_directory (dir);
702 copy_file ("test/data/subs1.xml", dir / "subs.xml");
703 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
704 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
705 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
708 Editor e (dir / "subs.xml");
709 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
712 check_verify_result (
715 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
718 dcp::VerificationNote::Type::ERROR,
719 dcp::VerificationNote::Code::INVALID_XML,
720 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
728 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
730 path const dir("build/test/verify_valid_smpte_subtitles");
731 prepare_directory (dir);
732 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
733 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
734 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
735 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
737 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
741 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
743 using namespace boost::filesystem;
745 path const dir("build/test/verify_invalid_smpte_subtitles");
746 prepare_directory (dir);
747 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
748 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
749 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
750 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
751 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
753 check_verify_result (
756 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
758 dcp::VerificationNote::Type::ERROR,
759 dcp::VerificationNote::Code::INVALID_XML,
760 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
764 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
765 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
770 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
772 path const dir("build/test/verify_empty_text_node_in_subtitles");
773 prepare_directory (dir);
774 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
775 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
776 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
777 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
779 check_verify_result (
782 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
783 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
784 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
790 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
791 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
793 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
794 prepare_directory (dir);
795 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
796 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
797 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
798 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
800 check_verify_result (
803 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
808 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
809 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
811 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
812 prepare_directory (dir);
813 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
814 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
815 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
816 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
818 check_verify_result (
821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
822 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
827 BOOST_AUTO_TEST_CASE (verify_external_asset)
829 path const ov_dir("build/test/verify_external_asset");
830 prepare_directory (ov_dir);
832 auto image = black_image ();
833 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
834 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
835 dcp_from_frame (frame, ov_dir);
837 dcp::DCP ov (ov_dir);
840 path const vf_dir("build/test/verify_external_asset_vf");
841 prepare_directory (vf_dir);
843 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
844 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
846 check_verify_result (
849 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
855 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
857 path const dir("build/test/verify_valid_cpl_metadata");
858 prepare_directory (dir);
860 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
861 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
862 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
864 auto reel = make_shared<dcp::Reel>();
865 reel->add (reel_asset);
867 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
868 reel->add (simple_markers(16 * 24));
870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
872 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873 cpl->set_main_sound_sample_rate (48000);
874 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
876 cpl->set_version_number (1);
881 dcp::String::compose("libdcp %1", dcp::version),
882 dcp::String::compose("libdcp %1", dcp::version),
883 dcp::LocalTime().as_string(),
889 path find_cpl (path dir)
891 for (auto i: directory_iterator(dir)) {
892 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
897 BOOST_REQUIRE (false);
902 /* DCP with invalid CompositionMetadataAsset */
903 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
905 using namespace boost::filesystem;
907 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
908 prepare_directory (dir);
910 auto reel = make_shared<dcp::Reel>();
911 reel->add (black_picture_asset(dir));
912 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
914 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
915 cpl->set_main_sound_sample_rate (48000);
916 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
917 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
918 cpl->set_version_number (1);
920 reel->add (simple_markers());
925 dcp::String::compose("libdcp %1", dcp::version),
926 dcp::String::compose("libdcp %1", dcp::version),
927 dcp::LocalTime().as_string(),
932 Editor e (find_cpl(dir));
933 e.replace ("MainSound", "MainSoundX");
936 check_verify_result (
939 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
940 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
942 dcp::VerificationNote::Type::ERROR,
943 dcp::VerificationNote::Code::INVALID_XML,
944 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
945 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
946 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
947 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
948 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
949 "ExtensionMetadataList?,)'"),
950 canonical(cpl->file().get()),
953 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
958 /* DCP with invalid CompositionMetadataAsset */
959 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
961 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
962 prepare_directory (dir);
964 auto reel = make_shared<dcp::Reel>();
965 reel->add (black_picture_asset(dir));
966 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
968 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
969 cpl->set_main_sound_sample_rate (48000);
970 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
971 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
976 dcp::String::compose("libdcp %1", dcp::version),
977 dcp::String::compose("libdcp %1", dcp::version),
978 dcp::LocalTime().as_string(),
983 Editor e (find_cpl(dir));
984 e.replace ("meta:Width", "meta:WidthX");
987 check_verify_result (
989 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
994 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
996 path const dir("build/test/verify_invalid_language1");
997 prepare_directory (dir);
998 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
999 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1000 asset->_language = "wrong-andbad";
1001 asset->write (dir / "subs.mxf");
1002 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1003 reel_asset->_language = "badlang";
1004 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1006 check_verify_result (
1009 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1010 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1011 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1016 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1017 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1019 path const dir("build/test/verify_invalid_language2");
1020 prepare_directory (dir);
1021 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1022 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1023 asset->_language = "wrong-andbad";
1024 asset->write (dir / "subs.mxf");
1025 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1026 reel_asset->_language = "badlang";
1027 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1029 check_verify_result (
1032 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1033 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1034 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1039 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1040 * the release territory.
1042 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1044 path const dir("build/test/verify_invalid_language3");
1045 prepare_directory (dir);
1047 auto picture = simple_picture (dir, "foo");
1048 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1049 auto reel = make_shared<dcp::Reel>();
1050 reel->add (reel_picture);
1051 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1052 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1053 reel->add (reel_sound);
1054 reel->add (simple_markers());
1056 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1058 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1059 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1060 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1061 cpl->set_main_sound_sample_rate (48000);
1062 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1063 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1064 cpl->set_version_number (1);
1065 cpl->_release_territory = "fred-jim";
1066 auto dcp = make_shared<dcp::DCP>(dir);
1069 dcp::String::compose("libdcp %1", dcp::version),
1070 dcp::String::compose("libdcp %1", dcp::version),
1071 dcp::LocalTime().as_string(),
1075 check_verify_result (
1078 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1079 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1080 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1081 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1087 vector<dcp::VerificationNote>
1088 check_picture_size (int width, int height, int frame_rate, bool three_d)
1090 using namespace boost::filesystem;
1092 path dcp_path = "build/test/verify_picture_test";
1093 prepare_directory (dcp_path);
1095 shared_ptr<dcp::PictureAsset> mp;
1097 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1099 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1101 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1103 auto image = black_image (dcp::Size(width, height));
1104 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1105 int const length = three_d ? frame_rate * 2 : frame_rate;
1106 for (int i = 0; i < length; ++i) {
1107 picture_writer->write (j2c.data(), j2c.size());
1109 picture_writer->finalize ();
1111 auto d = make_shared<dcp::DCP>(dcp_path);
1112 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1113 cpl->set_annotation_text ("A Test DCP");
1114 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1115 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1116 cpl->set_main_sound_sample_rate (48000);
1117 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1118 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1119 cpl->set_version_number (1);
1121 auto reel = make_shared<dcp::Reel>();
1124 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1126 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1129 reel->add (simple_markers(frame_rate));
1135 dcp::String::compose("libdcp %1", dcp::version),
1136 dcp::String::compose("libdcp %1", dcp::version),
1137 dcp::LocalTime().as_string(),
1141 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1147 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1149 auto notes = check_picture_size(width, height, frame_rate, three_d);
1150 BOOST_CHECK_EQUAL (notes.size(), 0U);
1156 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1158 auto notes = check_picture_size(width, height, frame_rate, three_d);
1159 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1160 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1161 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1167 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1169 auto notes = check_picture_size(width, height, frame_rate, three_d);
1170 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1171 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1172 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1178 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1180 auto notes = check_picture_size(width, height, frame_rate, three_d);
1181 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1182 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1183 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1187 BOOST_AUTO_TEST_CASE (verify_picture_size)
1189 using namespace boost::filesystem;
1192 check_picture_size_ok (2048, 858, 24, false);
1193 check_picture_size_ok (2048, 858, 25, false);
1194 check_picture_size_ok (2048, 858, 48, false);
1195 check_picture_size_ok (2048, 858, 24, true);
1196 check_picture_size_ok (2048, 858, 25, true);
1197 check_picture_size_ok (2048, 858, 48, true);
1200 check_picture_size_ok (1998, 1080, 24, false);
1201 check_picture_size_ok (1998, 1080, 25, false);
1202 check_picture_size_ok (1998, 1080, 48, false);
1203 check_picture_size_ok (1998, 1080, 24, true);
1204 check_picture_size_ok (1998, 1080, 25, true);
1205 check_picture_size_ok (1998, 1080, 48, true);
1208 check_picture_size_ok (4096, 1716, 24, false);
1211 check_picture_size_ok (3996, 2160, 24, false);
1213 /* Bad frame size */
1214 check_picture_size_bad_frame_size (2050, 858, 24, false);
1215 check_picture_size_bad_frame_size (2048, 658, 25, false);
1216 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1217 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1219 /* Bad 2K frame rate */
1220 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1221 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1222 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1224 /* Bad 4K frame rate */
1225 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1226 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1229 auto notes = check_picture_size(3996, 2160, 24, true);
1230 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1231 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1232 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1238 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")
1241 make_shared<dcp::SubtitleString>(
1249 dcp::Time(start_frame, 24, 24),
1250 dcp::Time(end_frame, 24, 24),
1252 dcp::HAlign::CENTER,
1255 dcp::Direction::LTR,
1267 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1269 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1270 prepare_directory (dir);
1272 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1273 for (int i = 0; i < 2048; ++i) {
1274 add_test_subtitle (asset, i * 24, i * 24 + 20);
1276 asset->set_language (dcp::LanguageTag("de-DE"));
1277 asset->write (dir / "subs.mxf");
1278 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1279 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1281 check_verify_result (
1284 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1286 dcp::VerificationNote::Type::BV21_ERROR,
1287 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1289 canonical(dir / "subs.mxf")
1291 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1292 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1298 shared_ptr<dcp::SMPTESubtitleAsset>
1299 make_large_subtitle_asset (path font_file)
1301 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1302 dcp::ArrayData big_fake_font(1024 * 1024);
1303 big_fake_font.write (font_file);
1304 for (int i = 0; i < 116; ++i) {
1305 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1313 verify_timed_text_asset_too_large (string name)
1315 auto const dir = path("build/test") / name;
1316 prepare_directory (dir);
1317 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1318 add_test_subtitle (asset, 0, 240);
1319 asset->set_language (dcp::LanguageTag("de-DE"));
1320 asset->write (dir / "subs.mxf");
1322 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1323 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1325 check_verify_result (
1328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695136"), canonical(dir / "subs.mxf") },
1329 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1330 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1331 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1337 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1339 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1340 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1344 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1346 path dir = "build/test/verify_missing_subtitle_language";
1347 prepare_directory (dir);
1348 auto dcp = make_simple (dir, 1, 106);
1351 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1352 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1353 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1354 "<ContentTitleText>Content</ContentTitleText>"
1355 "<AnnotationText>Annotation</AnnotationText>"
1356 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1357 "<ReelNumber>1</ReelNumber>"
1358 "<EditRate>24 1</EditRate>"
1359 "<TimeCodeRate>24</TimeCodeRate>"
1360 "<StartTime>00:00:00:00</StartTime>"
1361 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1363 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1364 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1365 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1371 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1372 BOOST_REQUIRE (xml_file);
1373 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1375 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1376 subs->write (dir / "subs.mxf");
1378 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1379 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1381 dcp::String::compose("libdcp %1", dcp::version),
1382 dcp::String::compose("libdcp %1", dcp::version),
1383 dcp::LocalTime().as_string(),
1387 check_verify_result (
1390 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1391 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1396 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1398 path path ("build/test/verify_mismatched_subtitle_languages");
1399 auto constexpr reel_length = 192;
1400 auto dcp = make_simple (path, 2, reel_length);
1401 auto cpl = dcp->cpls()[0];
1404 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1405 subs->set_language (dcp::LanguageTag("de-DE"));
1406 subs->add (simple_subtitle());
1407 subs->write (path / "subs1.mxf");
1408 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1409 cpl->reels()[0]->add(reel_subs);
1413 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1414 subs->set_language (dcp::LanguageTag("en-US"));
1415 subs->add (simple_subtitle());
1416 subs->write (path / "subs2.mxf");
1417 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1418 cpl->reels()[1]->add(reel_subs);
1422 dcp::String::compose("libdcp %1", dcp::version),
1423 dcp::String::compose("libdcp %1", dcp::version),
1424 dcp::LocalTime().as_string(),
1428 check_verify_result (
1431 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1432 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1438 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1440 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1441 auto constexpr reel_length = 192;
1442 auto dcp = make_simple (path, 2, reel_length);
1443 auto cpl = dcp->cpls()[0];
1446 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1447 ccaps->set_language (dcp::LanguageTag("de-DE"));
1448 ccaps->add (simple_subtitle());
1449 ccaps->write (path / "subs1.mxf");
1450 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1451 cpl->reels()[0]->add(reel_ccaps);
1455 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1456 ccaps->set_language (dcp::LanguageTag("en-US"));
1457 ccaps->add (simple_subtitle());
1458 ccaps->write (path / "subs2.mxf");
1459 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1460 cpl->reels()[1]->add(reel_ccaps);
1464 dcp::String::compose("libdcp %1", dcp::version),
1465 dcp::String::compose("libdcp %1", dcp::version),
1466 dcp::LocalTime().as_string(),
1470 check_verify_result (
1473 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1474 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1479 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1481 path dir = "build/test/verify_missing_subtitle_start_time";
1482 prepare_directory (dir);
1483 auto dcp = make_simple (dir, 1, 106);
1486 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1487 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1488 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1489 "<ContentTitleText>Content</ContentTitleText>"
1490 "<AnnotationText>Annotation</AnnotationText>"
1491 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1492 "<ReelNumber>1</ReelNumber>"
1493 "<Language>de-DE</Language>"
1494 "<EditRate>24 1</EditRate>"
1495 "<TimeCodeRate>24</TimeCodeRate>"
1496 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1498 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1499 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1500 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1506 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1507 BOOST_REQUIRE (xml_file);
1508 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1510 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1511 subs->write (dir / "subs.mxf");
1513 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1514 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1516 dcp::String::compose("libdcp %1", dcp::version),
1517 dcp::String::compose("libdcp %1", dcp::version),
1518 dcp::LocalTime().as_string(),
1522 check_verify_result (
1525 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1526 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1531 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1533 path dir = "build/test/verify_invalid_subtitle_start_time";
1534 prepare_directory (dir);
1535 auto dcp = make_simple (dir, 1, 106);
1538 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1539 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1540 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1541 "<ContentTitleText>Content</ContentTitleText>"
1542 "<AnnotationText>Annotation</AnnotationText>"
1543 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1544 "<ReelNumber>1</ReelNumber>"
1545 "<Language>de-DE</Language>"
1546 "<EditRate>24 1</EditRate>"
1547 "<TimeCodeRate>24</TimeCodeRate>"
1548 "<StartTime>00:00:02:00</StartTime>"
1549 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1551 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1552 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1553 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1559 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1560 BOOST_REQUIRE (xml_file);
1561 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1563 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1564 subs->write (dir / "subs.mxf");
1566 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1567 dcp->cpls().front()->reels().front()->add(reel_subs);
1569 dcp::String::compose("libdcp %1", dcp::version),
1570 dcp::String::compose("libdcp %1", dcp::version),
1571 dcp::LocalTime().as_string(),
1575 check_verify_result (
1578 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1579 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1587 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1590 , v_position(v_position_)
1598 dcp::VAlign v_align;
1604 shared_ptr<dcp::CPL>
1605 dcp_with_text (path dir, vector<TestText> subs)
1607 prepare_directory (dir);
1608 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1609 asset->set_start_time (dcp::Time());
1610 for (auto i: subs) {
1611 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1613 asset->set_language (dcp::LanguageTag("de-DE"));
1614 asset->write (dir / "subs.mxf");
1616 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1617 return write_dcp_with_single_asset (dir, reel_asset);
1621 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1623 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1624 /* Just too early */
1625 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1626 check_verify_result (
1629 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1630 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1636 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1638 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1639 /* Just late enough */
1640 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1641 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1645 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1647 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1648 prepare_directory (dir);
1650 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1651 asset1->set_start_time (dcp::Time());
1652 /* Just late enough */
1653 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1654 asset1->set_language (dcp::LanguageTag("de-DE"));
1655 asset1->write (dir / "subs1.mxf");
1656 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1657 auto reel1 = make_shared<dcp::Reel>();
1658 reel1->add (reel_asset1);
1659 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1660 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1661 reel1->add (markers1);
1663 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1664 asset2->set_start_time (dcp::Time());
1665 /* This would be too early on first reel but should be OK on the second */
1666 add_test_subtitle (asset2, 3, 4 * 24);
1667 asset2->set_language (dcp::LanguageTag("de-DE"));
1668 asset2->write (dir / "subs2.mxf");
1669 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1670 auto reel2 = make_shared<dcp::Reel>();
1671 reel2->add (reel_asset2);
1672 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1673 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1674 reel2->add (markers2);
1676 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1679 auto dcp = make_shared<dcp::DCP>(dir);
1682 dcp::String::compose("libdcp %1", dcp::version),
1683 dcp::String::compose("libdcp %1", dcp::version),
1684 dcp::LocalTime().as_string(),
1689 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1693 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1695 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1696 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1700 { 5 * 24 + 1, 6 * 24 },
1702 check_verify_result (
1705 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1711 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1713 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1714 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1718 { 5 * 24 + 16, 8 * 24 },
1720 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1724 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1726 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1727 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1728 check_verify_result (
1731 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1732 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1737 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1739 auto const dir = path("build/test/verify_valid_subtitle_duration");
1740 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1741 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1745 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1747 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1748 prepare_directory (dir);
1749 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1750 asset->set_start_time (dcp::Time());
1751 add_test_subtitle (asset, 0, 4 * 24);
1752 asset->set_language (dcp::LanguageTag("de-DE"));
1753 asset->write (dir / "subs.mxf");
1755 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1756 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1757 check_verify_result (
1760 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1761 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1762 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1763 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1769 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1771 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1772 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1775 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1776 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1777 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1778 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1780 check_verify_result (
1783 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1784 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1789 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1791 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1792 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1795 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1796 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1797 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1799 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1803 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1805 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1806 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1809 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1810 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1811 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1812 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1814 check_verify_result (
1817 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1818 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1823 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1825 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1826 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1829 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1830 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1831 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1832 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1834 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1838 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1840 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1841 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1844 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1846 check_verify_result (
1849 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1855 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1857 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1858 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1861 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1863 check_verify_result (
1866 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1867 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1872 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1874 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1875 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1878 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1879 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1880 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1881 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1883 check_verify_result (
1886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1892 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1894 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1895 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1898 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1899 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1900 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1902 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1906 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1908 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1909 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1912 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1913 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1914 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1915 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1917 check_verify_result (
1920 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1921 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1926 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1928 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1929 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1932 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1933 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1934 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1935 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1937 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1941 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1943 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1944 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1947 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
1949 check_verify_result (
1952 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1953 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1958 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1960 path const dir("build/test/verify_invalid_sound_frame_rate");
1961 prepare_directory (dir);
1963 auto picture = simple_picture (dir, "foo");
1964 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1965 auto reel = make_shared<dcp::Reel>();
1966 reel->add (reel_picture);
1967 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
1968 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1969 reel->add (reel_sound);
1970 reel->add (simple_markers());
1971 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1973 auto dcp = make_shared<dcp::DCP>(dir);
1976 dcp::String::compose("libdcp %1", dcp::version),
1977 dcp::String::compose("libdcp %1", dcp::version),
1978 dcp::LocalTime().as_string(),
1982 check_verify_result (
1985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1991 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1993 path const dir("build/test/verify_missing_cpl_annotation_text");
1994 auto dcp = make_simple (dir);
1996 dcp::String::compose("libdcp %1", dcp::version),
1997 dcp::String::compose("libdcp %1", dcp::version),
1998 dcp::LocalTime().as_string(),
2002 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2004 auto const cpl = dcp->cpls()[0];
2007 BOOST_REQUIRE (cpl->file());
2008 Editor e(cpl->file().get());
2009 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2012 check_verify_result (
2015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2016 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2021 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2023 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2024 auto dcp = make_simple (dir);
2026 dcp::String::compose("libdcp %1", dcp::version),
2027 dcp::String::compose("libdcp %1", dcp::version),
2028 dcp::LocalTime().as_string(),
2032 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2033 auto const cpl = dcp->cpls()[0];
2036 BOOST_REQUIRE (cpl->file());
2037 Editor e(cpl->file().get());
2038 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2041 check_verify_result (
2044 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2045 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2050 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2052 path const dir("build/test/verify_mismatched_asset_duration");
2053 prepare_directory (dir);
2054 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2055 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2057 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2058 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2060 auto reel = make_shared<dcp::Reel>(
2061 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2062 make_shared<dcp::ReelSoundAsset>(ms, 0)
2065 reel->add (simple_markers());
2070 dcp::String::compose("libdcp %1", dcp::version),
2071 dcp::String::compose("libdcp %1", dcp::version),
2072 dcp::LocalTime().as_string(),
2076 check_verify_result (
2079 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2080 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2087 shared_ptr<dcp::CPL>
2088 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2090 prepare_directory (dir);
2091 auto dcp = make_shared<dcp::DCP>(dir);
2092 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2094 auto constexpr reel_length = 192;
2096 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2097 subs->set_language (dcp::LanguageTag("de-DE"));
2098 subs->set_start_time (dcp::Time());
2099 subs->add (simple_subtitle());
2100 subs->write (dir / "subs.mxf");
2101 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2103 auto reel1 = make_shared<dcp::Reel>(
2104 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2105 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2109 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2112 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2113 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2114 reel1->add (markers1);
2118 auto reel2 = make_shared<dcp::Reel>(
2119 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2120 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2124 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2127 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2128 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2129 reel2->add (markers2);
2135 dcp::String::compose("libdcp %1", dcp::version),
2136 dcp::String::compose("libdcp %1", dcp::version),
2137 dcp::LocalTime().as_string(),
2145 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2148 path dir ("build/test/missing_main_subtitle_from_some_reels");
2149 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2150 check_verify_result (
2153 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2154 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2160 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2161 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2162 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2166 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2167 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2168 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2174 shared_ptr<dcp::CPL>
2175 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2177 prepare_directory (dir);
2178 auto dcp = make_shared<dcp::DCP>(dir);
2179 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2181 auto constexpr reel_length = 192;
2183 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2184 subs->set_language (dcp::LanguageTag("de-DE"));
2185 subs->set_start_time (dcp::Time());
2186 subs->add (simple_subtitle());
2187 subs->write (dir / "subs.mxf");
2189 auto reel1 = make_shared<dcp::Reel>(
2190 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2191 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2194 for (int i = 0; i < caps_in_reel1; ++i) {
2195 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2198 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2199 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2200 reel1->add (markers1);
2204 auto reel2 = make_shared<dcp::Reel>(
2205 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2206 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2209 for (int i = 0; i < caps_in_reel2; ++i) {
2210 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2213 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2214 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2215 reel2->add (markers2);
2221 dcp::String::compose("libdcp %1", dcp::version),
2222 dcp::String::compose("libdcp %1", dcp::version),
2223 dcp::LocalTime().as_string(),
2231 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2234 path dir ("build/test/mismatched_closed_caption_asset_counts");
2235 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2236 check_verify_result (
2239 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2240 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2245 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2246 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2247 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2251 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2252 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2253 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2260 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2262 prepare_directory (dir);
2263 auto dcp = make_shared<dcp::DCP>(dir);
2264 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2266 auto constexpr reel_length = 192;
2268 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2269 subs->set_language (dcp::LanguageTag("de-DE"));
2270 subs->set_start_time (dcp::Time());
2271 subs->add (simple_subtitle());
2272 subs->write (dir / "subs.mxf");
2273 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2276 auto reel = make_shared<dcp::Reel>(
2277 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2278 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2281 reel->add (reel_text);
2283 reel->add (simple_markers(reel_length));
2289 dcp::String::compose("libdcp %1", dcp::version),
2290 dcp::String::compose("libdcp %1", dcp::version),
2291 dcp::LocalTime().as_string(),
2295 check_verify_result (
2298 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2299 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2304 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2306 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2307 "build/test/verify_subtitle_entry_point_must_be_present",
2308 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2309 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2310 asset->unset_entry_point ();
2314 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2315 "build/test/verify_subtitle_entry_point_must_be_zero",
2316 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2317 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2318 asset->set_entry_point (4);
2322 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2323 "build/test/verify_closed_caption_entry_point_must_be_present",
2324 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2325 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2326 asset->unset_entry_point ();
2330 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2331 "build/test/verify_closed_caption_entry_point_must_be_zero",
2332 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2333 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2334 asset->set_entry_point (9);
2340 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2344 path const dir("build/test/verify_missing_hash");
2345 auto dcp = make_simple (dir);
2347 dcp::String::compose("libdcp %1", dcp::version),
2348 dcp::String::compose("libdcp %1", dcp::version),
2349 dcp::LocalTime().as_string(),
2353 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2354 auto const cpl = dcp->cpls()[0];
2355 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2356 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2357 auto asset_id = cpl->reels()[0]->main_picture()->id();
2360 BOOST_REQUIRE (cpl->file());
2361 Editor e(cpl->file().get());
2362 e.delete_first_line_containing("<Hash>");
2365 check_verify_result (
2368 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2369 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2376 verify_markers_test (
2378 vector<pair<dcp::Marker, dcp::Time>> markers,
2379 vector<dcp::VerificationNote> test_notes
2382 auto dcp = make_simple (dir);
2383 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2384 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2385 for (auto const& i: markers) {
2386 markers_asset->set (i.first, i.second);
2388 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2390 dcp::String::compose("libdcp %1", dcp::version),
2391 dcp::String::compose("libdcp %1", dcp::version),
2392 dcp::LocalTime().as_string(),
2396 check_verify_result ({dir}, test_notes);
2400 BOOST_AUTO_TEST_CASE (verify_markers)
2402 verify_markers_test (
2403 "build/test/verify_markers_all_correct",
2405 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2406 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2407 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2408 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2413 verify_markers_test (
2414 "build/test/verify_markers_missing_ffec",
2416 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2417 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2418 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2421 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2424 verify_markers_test (
2425 "build/test/verify_markers_missing_ffmc",
2427 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2428 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2429 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2432 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2435 verify_markers_test (
2436 "build/test/verify_markers_missing_ffoc",
2438 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2439 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2440 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2443 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2446 verify_markers_test (
2447 "build/test/verify_markers_missing_lfoc",
2449 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2450 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2451 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2454 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2457 verify_markers_test (
2458 "build/test/verify_markers_incorrect_ffoc",
2460 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2461 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2462 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2463 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2466 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2469 verify_markers_test (
2470 "build/test/verify_markers_incorrect_lfoc",
2472 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2473 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2474 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2475 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2478 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2483 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2485 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2486 prepare_directory (dir);
2487 auto dcp = make_simple (dir);
2488 auto cpl = dcp->cpls()[0];
2489 cpl->unset_version_number();
2491 dcp::String::compose("libdcp %1", dcp::version),
2492 dcp::String::compose("libdcp %1", dcp::version),
2493 dcp::LocalTime().as_string(),
2497 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2501 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2503 path dir = "build/test/verify_missing_extension_metadata1";
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 cpl = dcp->cpls()[0];
2516 Editor e (cpl->file().get());
2517 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2520 check_verify_result (
2523 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2524 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2529 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2531 path dir = "build/test/verify_missing_extension_metadata2";
2532 auto dcp = make_simple (dir);
2534 dcp::String::compose("libdcp %1", dcp::version),
2535 dcp::String::compose("libdcp %1", dcp::version),
2536 dcp::LocalTime().as_string(),
2540 auto cpl = dcp->cpls()[0];
2543 Editor e (cpl->file().get());
2544 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2547 check_verify_result (
2550 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2556 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2558 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2559 auto dcp = make_simple (dir);
2561 dcp::String::compose("libdcp %1", dcp::version),
2562 dcp::String::compose("libdcp %1", dcp::version),
2563 dcp::LocalTime().as_string(),
2567 auto const cpl = dcp->cpls()[0];
2570 Editor e (cpl->file().get());
2571 e.replace ("<meta:Name>A", "<meta:NameX>A");
2572 e.replace ("n</meta:Name>", "n</meta:NameX>");
2575 check_verify_result (
2578 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2579 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2580 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2585 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2587 path dir = "build/test/verify_invalid_extension_metadata1";
2588 auto dcp = make_simple (dir);
2590 dcp::String::compose("libdcp %1", dcp::version),
2591 dcp::String::compose("libdcp %1", dcp::version),
2592 dcp::LocalTime().as_string(),
2596 auto cpl = dcp->cpls()[0];
2599 Editor e (cpl->file().get());
2600 e.replace ("Application", "Fred");
2603 check_verify_result (
2606 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2607 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2612 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2614 path dir = "build/test/verify_invalid_extension_metadata2";
2615 auto dcp = make_simple (dir);
2617 dcp::String::compose("libdcp %1", dcp::version),
2618 dcp::String::compose("libdcp %1", dcp::version),
2619 dcp::LocalTime().as_string(),
2623 auto cpl = dcp->cpls()[0];
2626 Editor e (cpl->file().get());
2627 e.replace ("DCP Constraints Profile", "Fred");
2630 check_verify_result (
2633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2634 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2639 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2641 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2642 auto dcp = make_simple (dir);
2644 dcp::String::compose("libdcp %1", dcp::version),
2645 dcp::String::compose("libdcp %1", dcp::version),
2646 dcp::LocalTime().as_string(),
2650 auto const cpl = dcp->cpls()[0];
2653 Editor e (cpl->file().get());
2654 e.replace ("<meta:Value>", "<meta:ValueX>");
2655 e.replace ("</meta:Value>", "</meta:ValueX>");
2658 check_verify_result (
2661 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2662 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 },
2663 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2668 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2670 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2671 auto dcp = make_simple (dir);
2673 dcp::String::compose("libdcp %1", dcp::version),
2674 dcp::String::compose("libdcp %1", dcp::version),
2675 dcp::LocalTime().as_string(),
2679 auto const cpl = dcp->cpls()[0];
2682 Editor e (cpl->file().get());
2683 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2686 check_verify_result (
2689 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2690 { 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() },
2695 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2697 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2698 auto dcp = make_simple (dir);
2700 dcp::String::compose("libdcp %1", dcp::version),
2701 dcp::String::compose("libdcp %1", dcp::version),
2702 dcp::LocalTime().as_string(),
2706 auto const cpl = dcp->cpls()[0];
2709 Editor e (cpl->file().get());
2710 e.replace ("<meta:Property>", "<meta:PropertyX>");
2711 e.replace ("</meta:Property>", "</meta:PropertyX>");
2714 check_verify_result (
2717 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2718 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2719 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2724 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2726 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2727 auto dcp = make_simple (dir);
2729 dcp::String::compose("libdcp %1", dcp::version),
2730 dcp::String::compose("libdcp %1", dcp::version),
2731 dcp::LocalTime().as_string(),
2735 auto const cpl = dcp->cpls()[0];
2738 Editor e (cpl->file().get());
2739 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2740 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2743 check_verify_result (
2746 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2747 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2748 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2754 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2756 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2757 prepare_directory (dir);
2758 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2759 copy_file (i.path(), dir / i.path().filename());
2762 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2763 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2767 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2770 check_verify_result (
2773 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2775 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2777 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2778 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2779 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2780 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2785 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2787 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2788 prepare_directory (dir);
2789 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2790 copy_file (i.path(), dir / i.path().filename());
2793 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2794 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2797 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2800 check_verify_result (
2803 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2804 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2805 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2806 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2808 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2814 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2816 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2817 prepare_directory (dir);
2818 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2819 copy_file (i.path(), dir / i.path().filename());
2823 Editor e (dir / dcp_test1_pkl);
2824 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2827 check_verify_result ({dir}, {});
2831 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2833 path dir ("build/test/verify_must_not_be_partially_encrypted");
2834 prepare_directory (dir);
2838 auto signer = make_shared<dcp::CertificateChain>();
2839 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2840 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2841 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2842 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2844 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2848 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2851 auto writer = mp->start_write (dir / "video.mxf", false);
2852 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2853 for (int i = 0; i < 24; ++i) {
2854 writer->write (j2c.data(), j2c.size());
2856 writer->finalize ();
2858 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2860 auto reel = make_shared<dcp::Reel>(
2861 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2862 make_shared<dcp::ReelSoundAsset>(ms, 0)
2865 reel->add (simple_markers());
2869 cpl->set_content_version (
2870 {"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"}
2872 cpl->set_annotation_text ("A Test DCP");
2873 cpl->set_issuer ("OpenDCP 0.0.25");
2874 cpl->set_creator ("OpenDCP 0.0.25");
2875 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2876 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2877 cpl->set_main_sound_sample_rate (48000);
2878 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2879 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2880 cpl->set_version_number (1);
2884 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2886 check_verify_result (
2889 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2894 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2896 vector<dcp::VerificationNote> notes;
2897 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"));
2898 auto reader = picture.start_read ();
2899 auto frame = reader->get_frame (0);
2900 verify_j2k (frame, notes);
2901 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2905 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2907 vector<dcp::VerificationNote> notes;
2908 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2909 auto reader = picture.start_read ();
2910 auto frame = reader->get_frame (0);
2911 verify_j2k (frame, notes);
2912 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2916 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2918 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2919 prepare_directory (dir);
2920 auto dcp = make_simple (dir);
2922 vector<dcp::VerificationNote> notes;
2923 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2924 auto reader = picture.start_read ();
2925 auto frame = reader->get_frame (0);
2926 verify_j2k (frame, notes);
2927 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2931 /** Check that ResourceID and the XML ID being different is spotted */
2932 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2934 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2935 prepare_directory (dir);
2937 ASDCP::WriterInfo writer_info;
2938 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2941 auto mxf_id = dcp::make_uuid ();
2942 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2943 BOOST_REQUIRE (c == Kumu::UUID_Length);
2945 auto resource_id = dcp::make_uuid ();
2946 ASDCP::TimedText::TimedTextDescriptor descriptor;
2947 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2948 DCP_ASSERT (c == Kumu::UUID_Length);
2950 auto xml_id = dcp::make_uuid ();
2951 ASDCP::TimedText::MXFWriter writer;
2952 auto subs_mxf = dir / "subs.mxf";
2953 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2954 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2955 writer.WriteTimedTextResource (dcp::String::compose(
2956 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2957 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2958 "<Id>urn:uuid:%1</Id>"
2959 "<ContentTitleText>Content</ContentTitleText>"
2960 "<AnnotationText>Annotation</AnnotationText>"
2961 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2962 "<ReelNumber>1</ReelNumber>"
2963 "<Language>en-US</Language>"
2964 "<EditRate>25 1</EditRate>"
2965 "<TimeCodeRate>25</TimeCodeRate>"
2966 "<StartTime>00:00:00:00</StartTime>"
2968 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2969 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2970 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2979 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2980 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2982 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2984 check_verify_result (
2987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2988 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2989 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2995 /** Check that ResourceID and the MXF ID being the same is spotted */
2996 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2998 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2999 prepare_directory (dir);
3001 ASDCP::WriterInfo writer_info;
3002 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3005 auto mxf_id = dcp::make_uuid ();
3006 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3007 BOOST_REQUIRE (c == Kumu::UUID_Length);
3009 auto resource_id = mxf_id;
3010 ASDCP::TimedText::TimedTextDescriptor descriptor;
3011 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3012 DCP_ASSERT (c == Kumu::UUID_Length);
3014 auto xml_id = resource_id;
3015 ASDCP::TimedText::MXFWriter writer;
3016 auto subs_mxf = dir / "subs.mxf";
3017 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3018 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3019 writer.WriteTimedTextResource (dcp::String::compose(
3020 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3021 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3022 "<Id>urn:uuid:%1</Id>"
3023 "<ContentTitleText>Content</ContentTitleText>"
3024 "<AnnotationText>Annotation</AnnotationText>"
3025 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3026 "<ReelNumber>1</ReelNumber>"
3027 "<Language>en-US</Language>"
3028 "<EditRate>25 1</EditRate>"
3029 "<TimeCodeRate>25</TimeCodeRate>"
3030 "<StartTime>00:00:00:00</StartTime>"
3032 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3033 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3034 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3043 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3044 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3046 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3048 check_verify_result (
3051 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3052 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3053 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3054 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3059 /** Check a DCP with a 3D asset marked as 2D */
3060 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3062 check_verify_result (
3063 { private_test / "data" / "xm" },
3066 dcp::VerificationNote::Type::WARNING,
3067 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3070 dcp::VerificationNote::Type::BV21_ERROR,
3071 dcp::VerificationNote::Code::INVALID_STANDARD