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_valid_closed_caption_line_length)
1980 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1981 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1984 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1986 check_verify_result (
1989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1994 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1996 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1997 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2000 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2002 check_verify_result (
2005 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2006 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2011 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2013 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2014 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2017 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2018 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2019 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2021 check_verify_result (
2024 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2029 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2031 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2032 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2035 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2036 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2037 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2039 check_verify_result (
2042 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2043 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2048 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2050 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2051 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2054 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2055 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2056 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2058 check_verify_result (
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2066 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2068 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2069 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2072 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2073 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2074 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2076 check_verify_result (
2079 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2084 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2086 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2087 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2088 check_verify_result (
2091 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2092 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2097 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2099 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2100 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2101 check_verify_result (
2104 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2110 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2112 path const dir("build/test/verify_invalid_sound_frame_rate");
2113 prepare_directory (dir);
2115 auto picture = simple_picture (dir, "foo");
2116 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2117 auto reel = make_shared<dcp::Reel>();
2118 reel->add (reel_picture);
2119 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2120 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2121 reel->add (reel_sound);
2122 reel->add (simple_markers());
2123 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2125 auto dcp = make_shared<dcp::DCP>(dir);
2128 dcp::String::compose("libdcp %1", dcp::version),
2129 dcp::String::compose("libdcp %1", dcp::version),
2130 dcp::LocalTime().as_string(),
2134 check_verify_result (
2137 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2143 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2145 path const dir("build/test/verify_missing_cpl_annotation_text");
2146 auto dcp = make_simple (dir);
2148 dcp::String::compose("libdcp %1", dcp::version),
2149 dcp::String::compose("libdcp %1", dcp::version),
2150 dcp::LocalTime().as_string(),
2154 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2156 auto const cpl = dcp->cpls()[0];
2159 BOOST_REQUIRE (cpl->file());
2160 Editor e(cpl->file().get());
2161 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2164 check_verify_result (
2167 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2168 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2173 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2175 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2176 auto dcp = make_simple (dir);
2178 dcp::String::compose("libdcp %1", dcp::version),
2179 dcp::String::compose("libdcp %1", dcp::version),
2180 dcp::LocalTime().as_string(),
2184 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2185 auto const cpl = dcp->cpls()[0];
2188 BOOST_REQUIRE (cpl->file());
2189 Editor e(cpl->file().get());
2190 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2193 check_verify_result (
2196 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2197 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2202 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2204 path const dir("build/test/verify_mismatched_asset_duration");
2205 prepare_directory (dir);
2206 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2207 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2209 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2210 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2212 auto reel = make_shared<dcp::Reel>(
2213 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2214 make_shared<dcp::ReelSoundAsset>(ms, 0)
2217 reel->add (simple_markers());
2222 dcp::String::compose("libdcp %1", dcp::version),
2223 dcp::String::compose("libdcp %1", dcp::version),
2224 dcp::LocalTime().as_string(),
2228 check_verify_result (
2231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2239 shared_ptr<dcp::CPL>
2240 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2242 prepare_directory (dir);
2243 auto dcp = make_shared<dcp::DCP>(dir);
2244 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2246 auto constexpr reel_length = 192;
2248 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2249 subs->set_language (dcp::LanguageTag("de-DE"));
2250 subs->set_start_time (dcp::Time());
2251 subs->add (simple_subtitle());
2252 subs->write (dir / "subs.mxf");
2253 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2255 auto reel1 = make_shared<dcp::Reel>(
2256 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2257 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2261 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2264 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2265 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2266 reel1->add (markers1);
2270 auto reel2 = make_shared<dcp::Reel>(
2271 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2272 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2276 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2279 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2280 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2281 reel2->add (markers2);
2287 dcp::String::compose("libdcp %1", dcp::version),
2288 dcp::String::compose("libdcp %1", dcp::version),
2289 dcp::LocalTime().as_string(),
2297 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2300 path dir ("build/test/missing_main_subtitle_from_some_reels");
2301 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2302 check_verify_result (
2305 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2306 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2312 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2313 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2314 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2318 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2319 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2320 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2326 shared_ptr<dcp::CPL>
2327 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2329 prepare_directory (dir);
2330 auto dcp = make_shared<dcp::DCP>(dir);
2331 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2333 auto constexpr reel_length = 192;
2335 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2336 subs->set_language (dcp::LanguageTag("de-DE"));
2337 subs->set_start_time (dcp::Time());
2338 subs->add (simple_subtitle());
2339 subs->write (dir / "subs.mxf");
2341 auto reel1 = make_shared<dcp::Reel>(
2342 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2343 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2346 for (int i = 0; i < caps_in_reel1; ++i) {
2347 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2350 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2351 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2352 reel1->add (markers1);
2356 auto reel2 = make_shared<dcp::Reel>(
2357 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2358 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2361 for (int i = 0; i < caps_in_reel2; ++i) {
2362 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2365 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2366 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2367 reel2->add (markers2);
2373 dcp::String::compose("libdcp %1", dcp::version),
2374 dcp::String::compose("libdcp %1", dcp::version),
2375 dcp::LocalTime().as_string(),
2383 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2386 path dir ("build/test/mismatched_closed_caption_asset_counts");
2387 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2388 check_verify_result (
2391 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2392 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2397 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2398 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2399 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2403 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2404 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2405 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2412 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2414 prepare_directory (dir);
2415 auto dcp = make_shared<dcp::DCP>(dir);
2416 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2418 auto constexpr reel_length = 192;
2420 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2421 subs->set_language (dcp::LanguageTag("de-DE"));
2422 subs->set_start_time (dcp::Time());
2423 subs->add (simple_subtitle());
2424 subs->write (dir / "subs.mxf");
2425 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2428 auto reel = make_shared<dcp::Reel>(
2429 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2430 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2433 reel->add (reel_text);
2435 reel->add (simple_markers(reel_length));
2441 dcp::String::compose("libdcp %1", dcp::version),
2442 dcp::String::compose("libdcp %1", dcp::version),
2443 dcp::LocalTime().as_string(),
2447 check_verify_result (
2450 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2451 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2456 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2458 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2459 "build/test/verify_subtitle_entry_point_must_be_present",
2460 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2461 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2462 asset->unset_entry_point ();
2466 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2467 "build/test/verify_subtitle_entry_point_must_be_zero",
2468 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2469 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2470 asset->set_entry_point (4);
2474 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2475 "build/test/verify_closed_caption_entry_point_must_be_present",
2476 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2477 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2478 asset->unset_entry_point ();
2482 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2483 "build/test/verify_closed_caption_entry_point_must_be_zero",
2484 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2485 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2486 asset->set_entry_point (9);
2492 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2496 path const dir("build/test/verify_missing_hash");
2497 auto dcp = make_simple (dir);
2499 dcp::String::compose("libdcp %1", dcp::version),
2500 dcp::String::compose("libdcp %1", dcp::version),
2501 dcp::LocalTime().as_string(),
2505 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2506 auto const cpl = dcp->cpls()[0];
2507 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2508 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2509 auto asset_id = cpl->reels()[0]->main_picture()->id();
2512 BOOST_REQUIRE (cpl->file());
2513 Editor e(cpl->file().get());
2514 e.delete_first_line_containing("<Hash>");
2517 check_verify_result (
2520 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2521 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2528 verify_markers_test (
2530 vector<pair<dcp::Marker, dcp::Time>> markers,
2531 vector<dcp::VerificationNote> test_notes
2534 auto dcp = make_simple (dir);
2535 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2536 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2537 for (auto const& i: markers) {
2538 markers_asset->set (i.first, i.second);
2540 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2542 dcp::String::compose("libdcp %1", dcp::version),
2543 dcp::String::compose("libdcp %1", dcp::version),
2544 dcp::LocalTime().as_string(),
2548 check_verify_result ({dir}, test_notes);
2552 BOOST_AUTO_TEST_CASE (verify_markers)
2554 verify_markers_test (
2555 "build/test/verify_markers_all_correct",
2557 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2558 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2559 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2560 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2565 verify_markers_test (
2566 "build/test/verify_markers_missing_ffec",
2568 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2569 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2570 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2573 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2576 verify_markers_test (
2577 "build/test/verify_markers_missing_ffmc",
2579 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2580 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2581 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2584 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2587 verify_markers_test (
2588 "build/test/verify_markers_missing_ffoc",
2590 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2591 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2592 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2595 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2598 verify_markers_test (
2599 "build/test/verify_markers_missing_lfoc",
2601 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2602 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2603 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2606 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2609 verify_markers_test (
2610 "build/test/verify_markers_incorrect_ffoc",
2612 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2613 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2614 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2615 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2618 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2621 verify_markers_test (
2622 "build/test/verify_markers_incorrect_lfoc",
2624 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2625 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2626 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2627 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2630 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2635 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2637 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2638 prepare_directory (dir);
2639 auto dcp = make_simple (dir);
2640 auto cpl = dcp->cpls()[0];
2641 cpl->unset_version_number();
2643 dcp::String::compose("libdcp %1", dcp::version),
2644 dcp::String::compose("libdcp %1", dcp::version),
2645 dcp::LocalTime().as_string(),
2649 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2653 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2655 path dir = "build/test/verify_missing_extension_metadata1";
2656 auto dcp = make_simple (dir);
2658 dcp::String::compose("libdcp %1", dcp::version),
2659 dcp::String::compose("libdcp %1", dcp::version),
2660 dcp::LocalTime().as_string(),
2664 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2665 auto cpl = dcp->cpls()[0];
2668 Editor e (cpl->file().get());
2669 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2672 check_verify_result (
2675 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2681 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2683 path dir = "build/test/verify_missing_extension_metadata2";
2684 auto dcp = make_simple (dir);
2686 dcp::String::compose("libdcp %1", dcp::version),
2687 dcp::String::compose("libdcp %1", dcp::version),
2688 dcp::LocalTime().as_string(),
2692 auto cpl = dcp->cpls()[0];
2695 Editor e (cpl->file().get());
2696 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2699 check_verify_result (
2702 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2708 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2710 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2711 auto dcp = make_simple (dir);
2713 dcp::String::compose("libdcp %1", dcp::version),
2714 dcp::String::compose("libdcp %1", dcp::version),
2715 dcp::LocalTime().as_string(),
2719 auto const cpl = dcp->cpls()[0];
2722 Editor e (cpl->file().get());
2723 e.replace ("<meta:Name>A", "<meta:NameX>A");
2724 e.replace ("n</meta:Name>", "n</meta:NameX>");
2727 check_verify_result (
2730 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2731 { 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 },
2732 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2737 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2739 path dir = "build/test/verify_invalid_extension_metadata1";
2740 auto dcp = make_simple (dir);
2742 dcp::String::compose("libdcp %1", dcp::version),
2743 dcp::String::compose("libdcp %1", dcp::version),
2744 dcp::LocalTime().as_string(),
2748 auto cpl = dcp->cpls()[0];
2751 Editor e (cpl->file().get());
2752 e.replace ("Application", "Fred");
2755 check_verify_result (
2758 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2759 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2764 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2766 path dir = "build/test/verify_invalid_extension_metadata2";
2767 auto dcp = make_simple (dir);
2769 dcp::String::compose("libdcp %1", dcp::version),
2770 dcp::String::compose("libdcp %1", dcp::version),
2771 dcp::LocalTime().as_string(),
2775 auto cpl = dcp->cpls()[0];
2778 Editor e (cpl->file().get());
2779 e.replace ("DCP Constraints Profile", "Fred");
2782 check_verify_result (
2785 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2791 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2793 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2794 auto dcp = make_simple (dir);
2796 dcp::String::compose("libdcp %1", dcp::version),
2797 dcp::String::compose("libdcp %1", dcp::version),
2798 dcp::LocalTime().as_string(),
2802 auto const cpl = dcp->cpls()[0];
2805 Editor e (cpl->file().get());
2806 e.replace ("<meta:Value>", "<meta:ValueX>");
2807 e.replace ("</meta:Value>", "</meta:ValueX>");
2810 check_verify_result (
2813 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2814 { 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 },
2815 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2820 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2822 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2823 auto dcp = make_simple (dir);
2825 dcp::String::compose("libdcp %1", dcp::version),
2826 dcp::String::compose("libdcp %1", dcp::version),
2827 dcp::LocalTime().as_string(),
2831 auto const cpl = dcp->cpls()[0];
2834 Editor e (cpl->file().get());
2835 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2838 check_verify_result (
2841 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2842 { 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() },
2847 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2849 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2850 auto dcp = make_simple (dir);
2852 dcp::String::compose("libdcp %1", dcp::version),
2853 dcp::String::compose("libdcp %1", dcp::version),
2854 dcp::LocalTime().as_string(),
2858 auto const cpl = dcp->cpls()[0];
2861 Editor e (cpl->file().get());
2862 e.replace ("<meta:Property>", "<meta:PropertyX>");
2863 e.replace ("</meta:Property>", "</meta:PropertyX>");
2866 check_verify_result (
2869 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2870 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2871 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2876 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2878 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2879 auto dcp = make_simple (dir);
2881 dcp::String::compose("libdcp %1", dcp::version),
2882 dcp::String::compose("libdcp %1", dcp::version),
2883 dcp::LocalTime().as_string(),
2887 auto const cpl = dcp->cpls()[0];
2890 Editor e (cpl->file().get());
2891 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2892 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2895 check_verify_result (
2898 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2899 { 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 },
2900 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2906 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2908 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2909 prepare_directory (dir);
2910 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2911 copy_file (i.path(), dir / i.path().filename());
2914 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2915 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2919 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2922 check_verify_result (
2925 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2926 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2928 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2929 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2930 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2932 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2937 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2939 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2940 prepare_directory (dir);
2941 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2942 copy_file (i.path(), dir / i.path().filename());
2945 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2946 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2949 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2952 check_verify_result (
2955 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2956 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2957 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2958 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2959 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2960 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2961 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2966 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2968 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2969 prepare_directory (dir);
2970 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2971 copy_file (i.path(), dir / i.path().filename());
2975 Editor e (dir / dcp_test1_pkl);
2976 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2979 check_verify_result ({dir}, {});
2983 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2985 path dir ("build/test/verify_must_not_be_partially_encrypted");
2986 prepare_directory (dir);
2990 auto signer = make_shared<dcp::CertificateChain>();
2991 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2992 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2993 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2994 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2996 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
3000 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
3003 auto writer = mp->start_write (dir / "video.mxf", false);
3004 dcp::ArrayData j2c ("test/data/flat_red.j2c");
3005 for (int i = 0; i < 24; ++i) {
3006 writer->write (j2c.data(), j2c.size());
3008 writer->finalize ();
3010 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
3012 auto reel = make_shared<dcp::Reel>(
3013 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
3014 make_shared<dcp::ReelSoundAsset>(ms, 0)
3017 reel->add (simple_markers());
3021 cpl->set_content_version (
3022 {"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"}
3024 cpl->set_annotation_text ("A Test DCP");
3025 cpl->set_issuer ("OpenDCP 0.0.25");
3026 cpl->set_creator ("OpenDCP 0.0.25");
3027 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
3028 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
3029 cpl->set_main_sound_sample_rate (48000);
3030 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
3031 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
3032 cpl->set_version_number (1);
3036 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
3038 check_verify_result (
3041 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
3046 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
3048 vector<dcp::VerificationNote> notes;
3049 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"));
3050 auto reader = picture.start_read ();
3051 auto frame = reader->get_frame (0);
3052 verify_j2k (frame, notes);
3053 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3057 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3059 vector<dcp::VerificationNote> notes;
3060 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3061 auto reader = picture.start_read ();
3062 auto frame = reader->get_frame (0);
3063 verify_j2k (frame, notes);
3064 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3068 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3070 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3071 prepare_directory (dir);
3072 auto dcp = make_simple (dir);
3074 vector<dcp::VerificationNote> notes;
3075 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3076 auto reader = picture.start_read ();
3077 auto frame = reader->get_frame (0);
3078 verify_j2k (frame, notes);
3079 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3083 /** Check that ResourceID and the XML ID being different is spotted */
3084 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3086 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3087 prepare_directory (dir);
3089 ASDCP::WriterInfo writer_info;
3090 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3093 auto mxf_id = dcp::make_uuid ();
3094 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3095 BOOST_REQUIRE (c == Kumu::UUID_Length);
3097 auto resource_id = dcp::make_uuid ();
3098 ASDCP::TimedText::TimedTextDescriptor descriptor;
3099 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3100 DCP_ASSERT (c == Kumu::UUID_Length);
3102 auto xml_id = dcp::make_uuid ();
3103 ASDCP::TimedText::MXFWriter writer;
3104 auto subs_mxf = dir / "subs.mxf";
3105 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3106 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3107 writer.WriteTimedTextResource (dcp::String::compose(
3108 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3109 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3110 "<Id>urn:uuid:%1</Id>"
3111 "<ContentTitleText>Content</ContentTitleText>"
3112 "<AnnotationText>Annotation</AnnotationText>"
3113 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3114 "<ReelNumber>1</ReelNumber>"
3115 "<Language>en-US</Language>"
3116 "<EditRate>25 1</EditRate>"
3117 "<TimeCodeRate>25</TimeCodeRate>"
3118 "<StartTime>00:00:00:00</StartTime>"
3120 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3121 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3122 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3131 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3132 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3134 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3136 check_verify_result (
3139 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3140 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3141 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3147 /** Check that ResourceID and the MXF ID being the same is spotted */
3148 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3150 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3151 prepare_directory (dir);
3153 ASDCP::WriterInfo writer_info;
3154 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3157 auto mxf_id = dcp::make_uuid ();
3158 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3159 BOOST_REQUIRE (c == Kumu::UUID_Length);
3161 auto resource_id = mxf_id;
3162 ASDCP::TimedText::TimedTextDescriptor descriptor;
3163 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3164 DCP_ASSERT (c == Kumu::UUID_Length);
3166 auto xml_id = resource_id;
3167 ASDCP::TimedText::MXFWriter writer;
3168 auto subs_mxf = dir / "subs.mxf";
3169 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3170 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3171 writer.WriteTimedTextResource (dcp::String::compose(
3172 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3173 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3174 "<Id>urn:uuid:%1</Id>"
3175 "<ContentTitleText>Content</ContentTitleText>"
3176 "<AnnotationText>Annotation</AnnotationText>"
3177 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3178 "<ReelNumber>1</ReelNumber>"
3179 "<Language>en-US</Language>"
3180 "<EditRate>25 1</EditRate>"
3181 "<TimeCodeRate>25</TimeCodeRate>"
3182 "<StartTime>00:00:00:00</StartTime>"
3184 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3185 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3186 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3195 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3196 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3198 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3200 check_verify_result (
3203 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3204 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3205 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3206 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3211 /** Check a DCP with a 3D asset marked as 2D */
3212 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3214 check_verify_result (
3215 { private_test / "data" / "xm" },
3218 dcp::VerificationNote::Type::WARNING,
3219 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3222 dcp::VerificationNote::Type::BV21_ERROR,
3223 dcp::VerificationNote::Code::INVALID_STANDARD