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);
1622 shared_ptr<dcp::CPL>
1623 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1625 prepare_directory (dir);
1626 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1627 asset->set_start_time (dcp::Time());
1628 asset->set_language (dcp::LanguageTag("de-DE"));
1630 auto subs_mxf = dir / "subs.mxf";
1631 asset->write (subs_mxf);
1633 /* The call to write() puts the asset into the DCP correctly but it will have
1634 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1637 ASDCP::TimedText::MXFWriter writer;
1638 ASDCP::WriterInfo writer_info;
1639 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1641 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1642 DCP_ASSERT (c == Kumu::UUID_Length);
1643 ASDCP::TimedText::TimedTextDescriptor descriptor;
1644 descriptor.ContainerDuration = asset->intrinsic_duration();
1645 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1646 DCP_ASSERT (c == Kumu::UUID_Length);
1647 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1648 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1649 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1650 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1653 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1654 return write_dcp_with_single_asset (dir, reel_asset);
1658 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1660 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1661 /* Just too early */
1662 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1663 check_verify_result (
1666 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1667 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1673 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1675 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1676 /* Just late enough */
1677 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1678 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1682 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1684 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1685 prepare_directory (dir);
1687 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1688 asset1->set_start_time (dcp::Time());
1689 /* Just late enough */
1690 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1691 asset1->set_language (dcp::LanguageTag("de-DE"));
1692 asset1->write (dir / "subs1.mxf");
1693 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1694 auto reel1 = make_shared<dcp::Reel>();
1695 reel1->add (reel_asset1);
1696 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1697 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1698 reel1->add (markers1);
1700 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1701 asset2->set_start_time (dcp::Time());
1702 /* This would be too early on first reel but should be OK on the second */
1703 add_test_subtitle (asset2, 3, 4 * 24);
1704 asset2->set_language (dcp::LanguageTag("de-DE"));
1705 asset2->write (dir / "subs2.mxf");
1706 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1707 auto reel2 = make_shared<dcp::Reel>();
1708 reel2->add (reel_asset2);
1709 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1710 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1711 reel2->add (markers2);
1713 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1716 auto dcp = make_shared<dcp::DCP>(dir);
1719 dcp::String::compose("libdcp %1", dcp::version),
1720 dcp::String::compose("libdcp %1", dcp::version),
1721 dcp::LocalTime().as_string(),
1726 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1730 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1732 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1733 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1737 { 5 * 24 + 1, 6 * 24 },
1739 check_verify_result (
1742 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1743 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1748 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1750 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1751 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1755 { 5 * 24 + 16, 8 * 24 },
1757 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1761 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1763 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1764 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1765 check_verify_result (
1768 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1774 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1776 auto const dir = path("build/test/verify_valid_subtitle_duration");
1777 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1778 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1782 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1784 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1785 prepare_directory (dir);
1786 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1787 asset->set_start_time (dcp::Time());
1788 add_test_subtitle (asset, 0, 4 * 24);
1789 asset->set_language (dcp::LanguageTag("de-DE"));
1790 asset->write (dir / "subs.mxf");
1792 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1793 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1794 check_verify_result (
1797 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1798 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1806 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1808 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1809 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1812 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1813 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1814 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1815 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1817 check_verify_result (
1820 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1826 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1828 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1829 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1832 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1833 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1834 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1836 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1840 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1842 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1843 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1846 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1847 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1848 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1849 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1851 check_verify_result (
1854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1855 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1860 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1862 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1863 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1866 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1867 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1868 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1869 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1871 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1875 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1877 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1878 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1881 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1883 check_verify_result (
1886 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1892 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1894 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1895 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1898 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1900 check_verify_result (
1903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1904 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1909 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1911 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1912 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1915 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1916 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1917 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1918 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1920 check_verify_result (
1923 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1929 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1931 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1932 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1935 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1936 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1937 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1939 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1943 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1945 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1946 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1949 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1950 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1951 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1952 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1954 check_verify_result (
1957 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1958 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1963 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1965 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1966 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1969 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1970 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1971 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1972 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1974 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1978 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1980 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1981 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1984 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
1986 check_verify_result (
1989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1995 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
1997 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
1998 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2001 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2002 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2003 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2005 check_verify_result (
2008 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2013 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2015 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2016 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2019 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2020 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2021 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2023 check_verify_result (
2026 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2027 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2032 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2034 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2035 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2038 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2039 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2040 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2042 check_verify_result (
2045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2050 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2052 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2053 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2056 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2057 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2058 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2060 check_verify_result (
2063 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2068 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2070 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2071 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2072 check_verify_result (
2075 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2076 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2081 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2083 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2084 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2085 check_verify_result (
2088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2094 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2096 path const dir("build/test/verify_invalid_sound_frame_rate");
2097 prepare_directory (dir);
2099 auto picture = simple_picture (dir, "foo");
2100 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2101 auto reel = make_shared<dcp::Reel>();
2102 reel->add (reel_picture);
2103 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2104 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2105 reel->add (reel_sound);
2106 reel->add (simple_markers());
2107 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2109 auto dcp = make_shared<dcp::DCP>(dir);
2112 dcp::String::compose("libdcp %1", dcp::version),
2113 dcp::String::compose("libdcp %1", dcp::version),
2114 dcp::LocalTime().as_string(),
2118 check_verify_result (
2121 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2122 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2127 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2129 path const dir("build/test/verify_missing_cpl_annotation_text");
2130 auto dcp = make_simple (dir);
2132 dcp::String::compose("libdcp %1", dcp::version),
2133 dcp::String::compose("libdcp %1", dcp::version),
2134 dcp::LocalTime().as_string(),
2138 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2140 auto const cpl = dcp->cpls()[0];
2143 BOOST_REQUIRE (cpl->file());
2144 Editor e(cpl->file().get());
2145 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2148 check_verify_result (
2151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2152 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2157 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2159 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2160 auto dcp = make_simple (dir);
2162 dcp::String::compose("libdcp %1", dcp::version),
2163 dcp::String::compose("libdcp %1", dcp::version),
2164 dcp::LocalTime().as_string(),
2168 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2169 auto const cpl = dcp->cpls()[0];
2172 BOOST_REQUIRE (cpl->file());
2173 Editor e(cpl->file().get());
2174 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2177 check_verify_result (
2180 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2181 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2186 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2188 path const dir("build/test/verify_mismatched_asset_duration");
2189 prepare_directory (dir);
2190 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2191 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2193 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2194 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2196 auto reel = make_shared<dcp::Reel>(
2197 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2198 make_shared<dcp::ReelSoundAsset>(ms, 0)
2201 reel->add (simple_markers());
2206 dcp::String::compose("libdcp %1", dcp::version),
2207 dcp::String::compose("libdcp %1", dcp::version),
2208 dcp::LocalTime().as_string(),
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2216 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2223 shared_ptr<dcp::CPL>
2224 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2226 prepare_directory (dir);
2227 auto dcp = make_shared<dcp::DCP>(dir);
2228 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2230 auto constexpr reel_length = 192;
2232 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2233 subs->set_language (dcp::LanguageTag("de-DE"));
2234 subs->set_start_time (dcp::Time());
2235 subs->add (simple_subtitle());
2236 subs->write (dir / "subs.mxf");
2237 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2239 auto reel1 = make_shared<dcp::Reel>(
2240 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2241 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2245 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2248 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2249 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2250 reel1->add (markers1);
2254 auto reel2 = make_shared<dcp::Reel>(
2255 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2256 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2260 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2263 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2264 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2265 reel2->add (markers2);
2271 dcp::String::compose("libdcp %1", dcp::version),
2272 dcp::String::compose("libdcp %1", dcp::version),
2273 dcp::LocalTime().as_string(),
2281 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2284 path dir ("build/test/missing_main_subtitle_from_some_reels");
2285 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2286 check_verify_result (
2289 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2290 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2296 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2297 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2298 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2302 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2303 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2304 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2310 shared_ptr<dcp::CPL>
2311 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2313 prepare_directory (dir);
2314 auto dcp = make_shared<dcp::DCP>(dir);
2315 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2317 auto constexpr reel_length = 192;
2319 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2320 subs->set_language (dcp::LanguageTag("de-DE"));
2321 subs->set_start_time (dcp::Time());
2322 subs->add (simple_subtitle());
2323 subs->write (dir / "subs.mxf");
2325 auto reel1 = make_shared<dcp::Reel>(
2326 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2327 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2330 for (int i = 0; i < caps_in_reel1; ++i) {
2331 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2334 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2335 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2336 reel1->add (markers1);
2340 auto reel2 = make_shared<dcp::Reel>(
2341 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2342 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2345 for (int i = 0; i < caps_in_reel2; ++i) {
2346 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2349 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2350 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2351 reel2->add (markers2);
2357 dcp::String::compose("libdcp %1", dcp::version),
2358 dcp::String::compose("libdcp %1", dcp::version),
2359 dcp::LocalTime().as_string(),
2367 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2370 path dir ("build/test/mismatched_closed_caption_asset_counts");
2371 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2372 check_verify_result (
2375 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2381 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2382 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2383 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2387 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2388 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2389 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2396 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2398 prepare_directory (dir);
2399 auto dcp = make_shared<dcp::DCP>(dir);
2400 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2402 auto constexpr reel_length = 192;
2404 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2405 subs->set_language (dcp::LanguageTag("de-DE"));
2406 subs->set_start_time (dcp::Time());
2407 subs->add (simple_subtitle());
2408 subs->write (dir / "subs.mxf");
2409 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2412 auto reel = make_shared<dcp::Reel>(
2413 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2414 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2417 reel->add (reel_text);
2419 reel->add (simple_markers(reel_length));
2425 dcp::String::compose("libdcp %1", dcp::version),
2426 dcp::String::compose("libdcp %1", dcp::version),
2427 dcp::LocalTime().as_string(),
2431 check_verify_result (
2434 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2435 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2440 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2442 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2443 "build/test/verify_subtitle_entry_point_must_be_present",
2444 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2445 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2446 asset->unset_entry_point ();
2450 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2451 "build/test/verify_subtitle_entry_point_must_be_zero",
2452 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2453 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2454 asset->set_entry_point (4);
2458 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2459 "build/test/verify_closed_caption_entry_point_must_be_present",
2460 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2461 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2462 asset->unset_entry_point ();
2466 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2467 "build/test/verify_closed_caption_entry_point_must_be_zero",
2468 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2469 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2470 asset->set_entry_point (9);
2476 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2480 path const dir("build/test/verify_missing_hash");
2481 auto dcp = make_simple (dir);
2483 dcp::String::compose("libdcp %1", dcp::version),
2484 dcp::String::compose("libdcp %1", dcp::version),
2485 dcp::LocalTime().as_string(),
2489 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2490 auto const cpl = dcp->cpls()[0];
2491 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2492 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2493 auto asset_id = cpl->reels()[0]->main_picture()->id();
2496 BOOST_REQUIRE (cpl->file());
2497 Editor e(cpl->file().get());
2498 e.delete_first_line_containing("<Hash>");
2501 check_verify_result (
2504 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2505 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2512 verify_markers_test (
2514 vector<pair<dcp::Marker, dcp::Time>> markers,
2515 vector<dcp::VerificationNote> test_notes
2518 auto dcp = make_simple (dir);
2519 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2520 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2521 for (auto const& i: markers) {
2522 markers_asset->set (i.first, i.second);
2524 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2526 dcp::String::compose("libdcp %1", dcp::version),
2527 dcp::String::compose("libdcp %1", dcp::version),
2528 dcp::LocalTime().as_string(),
2532 check_verify_result ({dir}, test_notes);
2536 BOOST_AUTO_TEST_CASE (verify_markers)
2538 verify_markers_test (
2539 "build/test/verify_markers_all_correct",
2541 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2542 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2543 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2544 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2549 verify_markers_test (
2550 "build/test/verify_markers_missing_ffec",
2552 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2553 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2554 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2557 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2560 verify_markers_test (
2561 "build/test/verify_markers_missing_ffmc",
2563 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2564 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2565 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2568 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2571 verify_markers_test (
2572 "build/test/verify_markers_missing_ffoc",
2574 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2575 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2576 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2579 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2582 verify_markers_test (
2583 "build/test/verify_markers_missing_lfoc",
2585 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2586 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2587 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2590 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2593 verify_markers_test (
2594 "build/test/verify_markers_incorrect_ffoc",
2596 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2597 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2598 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2599 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2602 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2605 verify_markers_test (
2606 "build/test/verify_markers_incorrect_lfoc",
2608 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2609 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2610 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2611 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2614 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2619 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2621 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2622 prepare_directory (dir);
2623 auto dcp = make_simple (dir);
2624 auto cpl = dcp->cpls()[0];
2625 cpl->unset_version_number();
2627 dcp::String::compose("libdcp %1", dcp::version),
2628 dcp::String::compose("libdcp %1", dcp::version),
2629 dcp::LocalTime().as_string(),
2633 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2637 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2639 path dir = "build/test/verify_missing_extension_metadata1";
2640 auto dcp = make_simple (dir);
2642 dcp::String::compose("libdcp %1", dcp::version),
2643 dcp::String::compose("libdcp %1", dcp::version),
2644 dcp::LocalTime().as_string(),
2648 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2649 auto cpl = dcp->cpls()[0];
2652 Editor e (cpl->file().get());
2653 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2656 check_verify_result (
2659 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2660 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2665 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2667 path dir = "build/test/verify_missing_extension_metadata2";
2668 auto dcp = make_simple (dir);
2670 dcp::String::compose("libdcp %1", dcp::version),
2671 dcp::String::compose("libdcp %1", dcp::version),
2672 dcp::LocalTime().as_string(),
2676 auto cpl = dcp->cpls()[0];
2679 Editor e (cpl->file().get());
2680 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2683 check_verify_result (
2686 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2687 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2692 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2694 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2695 auto dcp = make_simple (dir);
2697 dcp::String::compose("libdcp %1", dcp::version),
2698 dcp::String::compose("libdcp %1", dcp::version),
2699 dcp::LocalTime().as_string(),
2703 auto const cpl = dcp->cpls()[0];
2706 Editor e (cpl->file().get());
2707 e.replace ("<meta:Name>A", "<meta:NameX>A");
2708 e.replace ("n</meta:Name>", "n</meta:NameX>");
2711 check_verify_result (
2714 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2715 { 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 },
2716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2721 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2723 path dir = "build/test/verify_invalid_extension_metadata1";
2724 auto dcp = make_simple (dir);
2726 dcp::String::compose("libdcp %1", dcp::version),
2727 dcp::String::compose("libdcp %1", dcp::version),
2728 dcp::LocalTime().as_string(),
2732 auto cpl = dcp->cpls()[0];
2735 Editor e (cpl->file().get());
2736 e.replace ("Application", "Fred");
2739 check_verify_result (
2742 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2743 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2748 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2750 path dir = "build/test/verify_invalid_extension_metadata2";
2751 auto dcp = make_simple (dir);
2753 dcp::String::compose("libdcp %1", dcp::version),
2754 dcp::String::compose("libdcp %1", dcp::version),
2755 dcp::LocalTime().as_string(),
2759 auto cpl = dcp->cpls()[0];
2762 Editor e (cpl->file().get());
2763 e.replace ("DCP Constraints Profile", "Fred");
2766 check_verify_result (
2769 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2770 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2775 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2777 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2778 auto dcp = make_simple (dir);
2780 dcp::String::compose("libdcp %1", dcp::version),
2781 dcp::String::compose("libdcp %1", dcp::version),
2782 dcp::LocalTime().as_string(),
2786 auto const cpl = dcp->cpls()[0];
2789 Editor e (cpl->file().get());
2790 e.replace ("<meta:Value>", "<meta:ValueX>");
2791 e.replace ("</meta:Value>", "</meta:ValueX>");
2794 check_verify_result (
2797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2798 { 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 },
2799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2804 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2806 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2807 auto dcp = make_simple (dir);
2809 dcp::String::compose("libdcp %1", dcp::version),
2810 dcp::String::compose("libdcp %1", dcp::version),
2811 dcp::LocalTime().as_string(),
2815 auto const cpl = dcp->cpls()[0];
2818 Editor e (cpl->file().get());
2819 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2822 check_verify_result (
2825 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2826 { 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() },
2831 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2833 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2834 auto dcp = make_simple (dir);
2836 dcp::String::compose("libdcp %1", dcp::version),
2837 dcp::String::compose("libdcp %1", dcp::version),
2838 dcp::LocalTime().as_string(),
2842 auto const cpl = dcp->cpls()[0];
2845 Editor e (cpl->file().get());
2846 e.replace ("<meta:Property>", "<meta:PropertyX>");
2847 e.replace ("</meta:Property>", "</meta:PropertyX>");
2850 check_verify_result (
2853 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2854 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2860 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2862 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2863 auto dcp = make_simple (dir);
2865 dcp::String::compose("libdcp %1", dcp::version),
2866 dcp::String::compose("libdcp %1", dcp::version),
2867 dcp::LocalTime().as_string(),
2871 auto const cpl = dcp->cpls()[0];
2874 Editor e (cpl->file().get());
2875 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2876 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2879 check_verify_result (
2882 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2883 { 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 },
2884 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2890 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2892 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2893 prepare_directory (dir);
2894 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2895 copy_file (i.path(), dir / i.path().filename());
2898 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2899 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2903 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2906 check_verify_result (
2909 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2910 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2913 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2914 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2921 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2923 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2924 prepare_directory (dir);
2925 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2926 copy_file (i.path(), dir / i.path().filename());
2929 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2930 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2933 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2936 check_verify_result (
2939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2940 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2941 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2942 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2943 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2944 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2945 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2950 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2952 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2953 prepare_directory (dir);
2954 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2955 copy_file (i.path(), dir / i.path().filename());
2959 Editor e (dir / dcp_test1_pkl);
2960 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2963 check_verify_result ({dir}, {});
2967 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2969 path dir ("build/test/verify_must_not_be_partially_encrypted");
2970 prepare_directory (dir);
2974 auto signer = make_shared<dcp::CertificateChain>();
2975 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2976 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2977 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2978 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2980 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2984 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2987 auto writer = mp->start_write (dir / "video.mxf", false);
2988 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2989 for (int i = 0; i < 24; ++i) {
2990 writer->write (j2c.data(), j2c.size());
2992 writer->finalize ();
2994 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2996 auto reel = make_shared<dcp::Reel>(
2997 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2998 make_shared<dcp::ReelSoundAsset>(ms, 0)
3001 reel->add (simple_markers());
3005 cpl->set_content_version (
3006 {"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"}
3008 cpl->set_annotation_text ("A Test DCP");
3009 cpl->set_issuer ("OpenDCP 0.0.25");
3010 cpl->set_creator ("OpenDCP 0.0.25");
3011 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3012 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3013 cpl->set_main_sound_sample_rate (48000);
3014 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3015 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3016 cpl->set_version_number (1);
3020 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
3022 check_verify_result (
3025 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3030 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3032 vector<dcp::VerificationNote> notes;
3033 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"));
3034 auto reader = picture.start_read ();
3035 auto frame = reader->get_frame (0);
3036 verify_j2k (frame, notes);
3037 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3041 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3043 vector<dcp::VerificationNote> notes;
3044 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3045 auto reader = picture.start_read ();
3046 auto frame = reader->get_frame (0);
3047 verify_j2k (frame, notes);
3048 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3052 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3054 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3055 prepare_directory (dir);
3056 auto dcp = make_simple (dir);
3058 vector<dcp::VerificationNote> notes;
3059 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3060 auto reader = picture.start_read ();
3061 auto frame = reader->get_frame (0);
3062 verify_j2k (frame, notes);
3063 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3067 /** Check that ResourceID and the XML ID being different is spotted */
3068 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3070 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3071 prepare_directory (dir);
3073 ASDCP::WriterInfo writer_info;
3074 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3077 auto mxf_id = dcp::make_uuid ();
3078 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3079 BOOST_REQUIRE (c == Kumu::UUID_Length);
3081 auto resource_id = dcp::make_uuid ();
3082 ASDCP::TimedText::TimedTextDescriptor descriptor;
3083 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3084 DCP_ASSERT (c == Kumu::UUID_Length);
3086 auto xml_id = dcp::make_uuid ();
3087 ASDCP::TimedText::MXFWriter writer;
3088 auto subs_mxf = dir / "subs.mxf";
3089 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3090 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3091 writer.WriteTimedTextResource (dcp::String::compose(
3092 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3093 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3094 "<Id>urn:uuid:%1</Id>"
3095 "<ContentTitleText>Content</ContentTitleText>"
3096 "<AnnotationText>Annotation</AnnotationText>"
3097 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3098 "<ReelNumber>1</ReelNumber>"
3099 "<Language>en-US</Language>"
3100 "<EditRate>25 1</EditRate>"
3101 "<TimeCodeRate>25</TimeCodeRate>"
3102 "<StartTime>00:00:00:00</StartTime>"
3104 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3105 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3106 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3115 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3116 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3118 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3120 check_verify_result (
3123 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3124 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3125 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3126 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3131 /** Check that ResourceID and the MXF ID being the same is spotted */
3132 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3134 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3135 prepare_directory (dir);
3137 ASDCP::WriterInfo writer_info;
3138 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3141 auto mxf_id = dcp::make_uuid ();
3142 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3143 BOOST_REQUIRE (c == Kumu::UUID_Length);
3145 auto resource_id = mxf_id;
3146 ASDCP::TimedText::TimedTextDescriptor descriptor;
3147 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3148 DCP_ASSERT (c == Kumu::UUID_Length);
3150 auto xml_id = resource_id;
3151 ASDCP::TimedText::MXFWriter writer;
3152 auto subs_mxf = dir / "subs.mxf";
3153 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3154 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3155 writer.WriteTimedTextResource (dcp::String::compose(
3156 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3157 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3158 "<Id>urn:uuid:%1</Id>"
3159 "<ContentTitleText>Content</ContentTitleText>"
3160 "<AnnotationText>Annotation</AnnotationText>"
3161 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3162 "<ReelNumber>1</ReelNumber>"
3163 "<Language>en-US</Language>"
3164 "<EditRate>25 1</EditRate>"
3165 "<TimeCodeRate>25</TimeCodeRate>"
3166 "<StartTime>00:00:00:00</StartTime>"
3168 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3169 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3170 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3179 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3180 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3182 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3184 check_verify_result (
3187 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3189 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3195 /** Check a DCP with a 3D asset marked as 2D */
3196 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3198 check_verify_result (
3199 { private_test / "data" / "xm" },
3202 dcp::VerificationNote::Type::WARNING,
3203 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3206 dcp::VerificationNote::Type::BV21_ERROR,
3207 dcp::VerificationNote::Code::INVALID_STANDARD