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 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
243 std::sort (notes.begin(), notes.end());
244 std::sort (test_notes.begin(), test_notes.end());
245 for (auto i = 0U; i < notes.size(); ++i) {
246 BOOST_REQUIRE_MESSAGE (notes[i] == test_notes[i], "Note from verify:\n" << notes[i] << "\ndoes not match the expected:\n" << test_notes[i]);
253 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
255 auto dir = setup (1, suffix);
258 Editor e (file(suffix));
259 e.replace (from, to);
262 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
264 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
265 auto i = notes.begin();
266 auto j = codes.begin();
267 while (i != notes.end()) {
268 BOOST_CHECK_EQUAL (i->code(), *j);
275 BOOST_AUTO_TEST_CASE (verify_no_error)
278 auto dir = setup (1, "no_error");
279 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
281 path const cpl_file = dir / dcp_test1_cpl;
282 path const pkl_file = dir / dcp_test1_pkl;
283 path const assetmap_file = dir / "ASSETMAP.xml";
285 auto st = stages.begin();
286 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
287 BOOST_REQUIRE (st->second);
288 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
290 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
291 BOOST_REQUIRE (st->second);
292 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
294 BOOST_CHECK_EQUAL (st->first, "Checking reel");
295 BOOST_REQUIRE (!st->second);
297 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
298 BOOST_REQUIRE (st->second);
299 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
301 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
302 BOOST_REQUIRE (st->second);
303 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
305 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
306 BOOST_REQUIRE (st->second);
307 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
309 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
310 BOOST_REQUIRE (st->second);
311 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
313 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
314 BOOST_REQUIRE (st->second);
315 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
317 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
318 BOOST_REQUIRE (st->second);
319 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
321 BOOST_REQUIRE (st == stages.end());
323 BOOST_CHECK_EQUAL (notes.size(), 0);
327 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
329 using namespace boost::filesystem;
331 auto dir = setup (1, "incorrect_picture_sound_hash");
333 auto video_path = path(dir / "video.mxf");
334 auto mod = fopen(video_path.string().c_str(), "r+b");
336 fseek (mod, 4096, SEEK_SET);
338 fwrite (&x, sizeof(x), 1, mod);
341 auto audio_path = path(dir / "audio.mxf");
342 mod = fopen(audio_path.string().c_str(), "r+b");
344 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
345 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
348 dcp::ASDCPErrorSuspender sus;
349 check_verify_result (
352 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
353 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
358 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
360 using namespace boost::filesystem;
362 auto dir = setup (1, "mismatched_picture_sound_hashes");
365 Editor e (dir / dcp_test1_pkl);
366 e.replace ("<Hash>", "<Hash>x");
369 check_verify_result (
372 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
373 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
374 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
375 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xLq7ot/GobgrqUYdlbR8FCD5APqs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
376 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xgVKhC9IkWyzQbgzpFcJ1bpqbtwk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
377 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xc1DRq6GaSzV2brF0YnSNed46nqk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 }
382 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
384 auto dir = setup (1, "failed_read_content_kind");
387 Editor e (dir / dcp_test1_cpl);
388 e.replace ("<ContentKind>", "<ContentKind>x");
391 check_verify_result (
393 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
402 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
410 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
416 asset_map (string suffix)
418 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
422 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
424 check_verify_result_after_replace (
425 "invalid_picture_frame_rate", &cpl,
426 "<FrameRate>24 1", "<FrameRate>99 1",
427 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
428 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
432 BOOST_AUTO_TEST_CASE (verify_missing_asset)
434 auto dir = setup (1, "missing_asset");
435 remove (dir / "video.mxf");
436 check_verify_result (
439 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
444 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
446 check_verify_result_after_replace (
447 "empty_asset_path", &asset_map,
448 "<Path>video.mxf</Path>", "<Path></Path>",
449 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
454 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
456 check_verify_result_after_replace (
457 "mismatched_standard", &cpl,
458 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
459 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
460 dcp::VerificationNote::Code::INVALID_XML,
461 dcp::VerificationNote::Code::INVALID_XML,
462 dcp::VerificationNote::Code::INVALID_XML,
463 dcp::VerificationNote::Code::INVALID_XML,
464 dcp::VerificationNote::Code::INVALID_XML,
465 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
470 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
472 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
473 check_verify_result_after_replace (
474 "invalid_xml_cpl_id", &cpl,
475 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
476 { dcp::VerificationNote::Code::INVALID_XML }
481 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
483 check_verify_result_after_replace (
484 "invalid_xml_issue_date", &cpl,
485 "<IssueDate>", "<IssueDate>x",
486 { dcp::VerificationNote::Code::INVALID_XML,
487 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
492 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
494 check_verify_result_after_replace (
495 "invalid_xml_pkl_id", &pkl,
496 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
497 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
498 { dcp::VerificationNote::Code::INVALID_XML }
503 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
505 check_verify_result_after_replace (
506 "invalix_xml_asset_map_id", &asset_map,
507 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
508 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
509 { dcp::VerificationNote::Code::INVALID_XML }
514 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
517 auto dir = setup (3, "verify_invalid_standard");
518 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
520 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
521 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
522 path const assetmap_file = dir / "ASSETMAP";
524 auto st = stages.begin();
525 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
526 BOOST_REQUIRE (st->second);
527 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
529 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
530 BOOST_REQUIRE (st->second);
531 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
533 BOOST_CHECK_EQUAL (st->first, "Checking reel");
534 BOOST_REQUIRE (!st->second);
536 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
537 BOOST_REQUIRE (st->second);
538 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
540 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
541 BOOST_REQUIRE (st->second);
542 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
544 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
545 BOOST_REQUIRE (st->second);
546 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
548 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
549 BOOST_REQUIRE (st->second);
550 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
552 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
553 BOOST_REQUIRE (st->second);
554 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
556 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
557 BOOST_REQUIRE (st->second);
558 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
560 BOOST_REQUIRE (st == stages.end());
562 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
563 auto i = notes.begin ();
564 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
565 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
567 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
568 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
571 /* DCP with a short asset */
572 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
574 auto dir = setup (8, "invalid_duration");
575 check_verify_result (
578 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
579 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
580 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
581 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
582 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
583 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
590 dcp_from_frame (dcp::ArrayData const& frame, path dir)
592 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
593 create_directories (dir);
594 auto writer = asset->start_write (dir / "pic.mxf", true);
595 for (int i = 0; i < 24; ++i) {
596 writer->write (frame.data(), frame.size());
600 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
601 return write_dcp_with_single_asset (dir, reel_asset);
605 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
607 int const too_big = 1302083 * 2;
609 /* Compress a black image */
610 auto image = black_image ();
611 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
612 BOOST_REQUIRE (frame.size() < too_big);
614 /* Place it in a bigger block with some zero padding at the end */
615 dcp::ArrayData oversized_frame(too_big);
616 memcpy (oversized_frame.data(), frame.data(), frame.size());
617 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
619 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
620 prepare_directory (dir);
621 auto cpl = dcp_from_frame (oversized_frame, dir);
623 check_verify_result (
626 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
627 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
628 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
633 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
635 int const nearly_too_big = 1302083 * 0.98;
637 /* Compress a black image */
638 auto image = black_image ();
639 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
640 BOOST_REQUIRE (frame.size() < nearly_too_big);
642 /* Place it in a bigger block with some zero padding at the end */
643 dcp::ArrayData oversized_frame(nearly_too_big);
644 memcpy (oversized_frame.data(), frame.data(), frame.size());
645 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
647 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
648 prepare_directory (dir);
649 auto cpl = dcp_from_frame (oversized_frame, dir);
651 check_verify_result (
654 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
655 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
656 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
661 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
663 /* Compress a black image */
664 auto image = black_image ();
665 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
666 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
668 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
669 prepare_directory (dir);
670 auto cpl = dcp_from_frame (frame, dir);
672 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
676 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
678 path const dir("build/test/verify_valid_interop_subtitles");
679 prepare_directory (dir);
680 copy_file ("test/data/subs1.xml", dir / "subs.xml");
681 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
682 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
683 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
685 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
689 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
691 using namespace boost::filesystem;
693 path const dir("build/test/verify_invalid_interop_subtitles");
694 prepare_directory (dir);
695 copy_file ("test/data/subs1.xml", dir / "subs.xml");
696 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
697 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
698 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
701 Editor e (dir / "subs.xml");
702 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
705 check_verify_result (
708 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
709 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
711 dcp::VerificationNote::Type::ERROR,
712 dcp::VerificationNote::Code::INVALID_XML,
713 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
721 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
723 path const dir("build/test/verify_valid_smpte_subtitles");
724 prepare_directory (dir);
725 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
726 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
727 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
728 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
730 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
734 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
736 using namespace boost::filesystem;
738 path const dir("build/test/verify_invalid_smpte_subtitles");
739 prepare_directory (dir);
740 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
741 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
742 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
743 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
744 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
746 check_verify_result (
749 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
751 dcp::VerificationNote::Type::ERROR,
752 dcp::VerificationNote::Code::INVALID_XML,
753 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
758 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
763 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
765 path const dir("build/test/verify_empty_text_node_in_subtitles");
766 prepare_directory (dir);
767 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
768 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
769 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
770 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
772 check_verify_result (
775 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
776 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
778 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
783 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
784 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
786 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
787 prepare_directory (dir);
788 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
789 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
790 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
791 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
793 check_verify_result (
796 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
801 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
802 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
804 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
805 prepare_directory (dir);
806 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
807 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
808 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
809 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
811 check_verify_result (
814 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
815 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
820 BOOST_AUTO_TEST_CASE (verify_external_asset)
822 path const ov_dir("build/test/verify_external_asset");
823 prepare_directory (ov_dir);
825 auto image = black_image ();
826 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
827 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
828 dcp_from_frame (frame, ov_dir);
830 dcp::DCP ov (ov_dir);
833 path const vf_dir("build/test/verify_external_asset_vf");
834 prepare_directory (vf_dir);
836 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
837 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
839 check_verify_result (
842 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
843 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
848 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
850 path const dir("build/test/verify_valid_cpl_metadata");
851 prepare_directory (dir);
853 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
854 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
855 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
857 auto reel = make_shared<dcp::Reel>();
858 reel->add (reel_asset);
860 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
861 reel->add (simple_markers(16 * 24));
863 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
865 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
866 cpl->set_main_sound_sample_rate (48000);
867 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
868 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
869 cpl->set_version_number (1);
874 dcp::String::compose("libdcp %1", dcp::version),
875 dcp::String::compose("libdcp %1", dcp::version),
876 dcp::LocalTime().as_string(),
882 path find_cpl (path dir)
884 for (auto i: directory_iterator(dir)) {
885 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
890 BOOST_REQUIRE (false);
895 /* DCP with invalid CompositionMetadataAsset */
896 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
898 using namespace boost::filesystem;
900 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
901 prepare_directory (dir);
903 auto reel = make_shared<dcp::Reel>();
904 reel->add (black_picture_asset(dir));
905 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
907 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
908 cpl->set_main_sound_sample_rate (48000);
909 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
910 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
911 cpl->set_version_number (1);
913 reel->add (simple_markers());
918 dcp::String::compose("libdcp %1", dcp::version),
919 dcp::String::compose("libdcp %1", dcp::version),
920 dcp::LocalTime().as_string(),
925 Editor e (find_cpl(dir));
926 e.replace ("MainSound", "MainSoundX");
929 check_verify_result (
932 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
933 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
935 dcp::VerificationNote::Type::ERROR,
936 dcp::VerificationNote::Code::INVALID_XML,
937 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
938 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
939 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
940 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
941 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
942 "ExtensionMetadataList?,)'"),
943 canonical(cpl->file().get()),
946 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
951 /* DCP with invalid CompositionMetadataAsset */
952 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
954 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
955 prepare_directory (dir);
957 auto reel = make_shared<dcp::Reel>();
958 reel->add (black_picture_asset(dir));
959 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
961 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
962 cpl->set_main_sound_sample_rate (48000);
963 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
964 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
969 dcp::String::compose("libdcp %1", dcp::version),
970 dcp::String::compose("libdcp %1", dcp::version),
971 dcp::LocalTime().as_string(),
976 Editor e (find_cpl(dir));
977 e.replace ("meta:Width", "meta:WidthX");
980 check_verify_result (
982 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
987 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
989 path const dir("build/test/verify_invalid_language1");
990 prepare_directory (dir);
991 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
992 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
993 asset->_language = "wrong-andbad";
994 asset->write (dir / "subs.mxf");
995 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
996 reel_asset->_language = "badlang";
997 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
999 check_verify_result (
1002 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1003 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1004 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1009 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1010 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1012 path const dir("build/test/verify_invalid_language2");
1013 prepare_directory (dir);
1014 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1015 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1016 asset->_language = "wrong-andbad";
1017 asset->write (dir / "subs.mxf");
1018 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1019 reel_asset->_language = "badlang";
1020 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1022 check_verify_result (
1025 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1026 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1027 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1032 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1033 * the release territory.
1035 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1037 path const dir("build/test/verify_invalid_language3");
1038 prepare_directory (dir);
1040 auto picture = simple_picture (dir, "foo");
1041 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1042 auto reel = make_shared<dcp::Reel>();
1043 reel->add (reel_picture);
1044 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1045 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1046 reel->add (reel_sound);
1047 reel->add (simple_markers());
1049 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1051 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1052 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1053 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1054 cpl->set_main_sound_sample_rate (48000);
1055 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1056 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1057 cpl->set_version_number (1);
1058 cpl->_release_territory = "fred-jim";
1059 auto dcp = make_shared<dcp::DCP>(dir);
1062 dcp::String::compose("libdcp %1", dcp::version),
1063 dcp::String::compose("libdcp %1", dcp::version),
1064 dcp::LocalTime().as_string(),
1068 check_verify_result (
1071 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1072 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1073 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1074 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1080 vector<dcp::VerificationNote>
1081 check_picture_size (int width, int height, int frame_rate, bool three_d)
1083 using namespace boost::filesystem;
1085 path dcp_path = "build/test/verify_picture_test";
1086 prepare_directory (dcp_path);
1088 shared_ptr<dcp::PictureAsset> mp;
1090 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1092 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1094 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1096 auto image = black_image (dcp::Size(width, height));
1097 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1098 int const length = three_d ? frame_rate * 2 : frame_rate;
1099 for (int i = 0; i < length; ++i) {
1100 picture_writer->write (j2c.data(), j2c.size());
1102 picture_writer->finalize ();
1104 auto d = make_shared<dcp::DCP>(dcp_path);
1105 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1106 cpl->set_annotation_text ("A Test DCP");
1107 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1108 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1109 cpl->set_main_sound_sample_rate (48000);
1110 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1111 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1112 cpl->set_version_number (1);
1114 auto reel = make_shared<dcp::Reel>();
1117 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1119 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1122 reel->add (simple_markers(frame_rate));
1128 dcp::String::compose("libdcp %1", dcp::version),
1129 dcp::String::compose("libdcp %1", dcp::version),
1130 dcp::LocalTime().as_string(),
1134 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1140 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1142 auto notes = check_picture_size(width, height, frame_rate, three_d);
1143 BOOST_CHECK_EQUAL (notes.size(), 0U);
1149 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1151 auto notes = check_picture_size(width, height, frame_rate, three_d);
1152 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1153 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1154 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1160 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1162 auto notes = check_picture_size(width, height, frame_rate, three_d);
1163 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1164 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1165 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1171 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1173 auto notes = check_picture_size(width, height, frame_rate, three_d);
1174 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1175 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1176 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1180 BOOST_AUTO_TEST_CASE (verify_picture_size)
1182 using namespace boost::filesystem;
1185 check_picture_size_ok (2048, 858, 24, false);
1186 check_picture_size_ok (2048, 858, 25, false);
1187 check_picture_size_ok (2048, 858, 48, false);
1188 check_picture_size_ok (2048, 858, 24, true);
1189 check_picture_size_ok (2048, 858, 25, true);
1190 check_picture_size_ok (2048, 858, 48, true);
1193 check_picture_size_ok (1998, 1080, 24, false);
1194 check_picture_size_ok (1998, 1080, 25, false);
1195 check_picture_size_ok (1998, 1080, 48, false);
1196 check_picture_size_ok (1998, 1080, 24, true);
1197 check_picture_size_ok (1998, 1080, 25, true);
1198 check_picture_size_ok (1998, 1080, 48, true);
1201 check_picture_size_ok (4096, 1716, 24, false);
1204 check_picture_size_ok (3996, 2160, 24, false);
1206 /* Bad frame size */
1207 check_picture_size_bad_frame_size (2050, 858, 24, false);
1208 check_picture_size_bad_frame_size (2048, 658, 25, false);
1209 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1210 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1212 /* Bad 2K frame rate */
1213 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1214 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1215 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1217 /* Bad 4K frame rate */
1218 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1219 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1222 auto notes = check_picture_size(3996, 2160, 24, true);
1223 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1224 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1225 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1231 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1234 make_shared<dcp::SubtitleString>(
1242 dcp::Time(start_frame, 24, 24),
1243 dcp::Time(end_frame, 24, 24),
1245 dcp::HAlign::CENTER,
1247 dcp::VAlign::CENTER,
1248 dcp::Direction::LTR,
1259 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1261 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1262 prepare_directory (dir);
1264 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1265 for (int i = 0; i < 2048; ++i) {
1266 add_test_subtitle (asset, i * 24, i * 24 + 20);
1268 asset->set_language (dcp::LanguageTag("de-DE"));
1269 asset->write (dir / "subs.mxf");
1270 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1271 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1273 check_verify_result (
1276 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1278 dcp::VerificationNote::Type::BV21_ERROR,
1279 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1281 canonical(dir / "subs.mxf")
1283 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1284 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1290 shared_ptr<dcp::SMPTESubtitleAsset>
1291 make_large_subtitle_asset (path font_file)
1293 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1294 dcp::ArrayData big_fake_font(1024 * 1024);
1295 big_fake_font.write (font_file);
1296 for (int i = 0; i < 116; ++i) {
1297 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1305 verify_timed_text_asset_too_large (string name)
1307 auto const dir = path("build/test") / name;
1308 prepare_directory (dir);
1309 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1310 add_test_subtitle (asset, 0, 240);
1311 asset->set_language (dcp::LanguageTag("de-DE"));
1312 asset->write (dir / "subs.mxf");
1314 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1315 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1317 check_verify_result (
1320 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1321 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1322 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1323 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1324 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1329 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1331 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1332 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1336 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1338 path dir = "build/test/verify_missing_subtitle_language";
1339 prepare_directory (dir);
1340 auto dcp = make_simple (dir, 1, 106);
1343 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1344 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1345 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1346 "<ContentTitleText>Content</ContentTitleText>"
1347 "<AnnotationText>Annotation</AnnotationText>"
1348 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1349 "<ReelNumber>1</ReelNumber>"
1350 "<EditRate>24 1</EditRate>"
1351 "<TimeCodeRate>24</TimeCodeRate>"
1352 "<StartTime>00:00:00:00</StartTime>"
1353 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1355 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1356 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1357 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1363 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1364 BOOST_REQUIRE (xml_file);
1365 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1367 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1368 subs->write (dir / "subs.mxf");
1370 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1371 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1373 dcp::String::compose("libdcp %1", dcp::version),
1374 dcp::String::compose("libdcp %1", dcp::version),
1375 dcp::LocalTime().as_string(),
1379 check_verify_result (
1382 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1383 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1388 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1390 path path ("build/test/verify_mismatched_subtitle_languages");
1391 auto constexpr reel_length = 192;
1392 auto dcp = make_simple (path, 2, reel_length);
1393 auto cpl = dcp->cpls()[0];
1396 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1397 subs->set_language (dcp::LanguageTag("de-DE"));
1398 subs->add (simple_subtitle());
1399 subs->write (path / "subs1.mxf");
1400 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1401 cpl->reels()[0]->add(reel_subs);
1405 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1406 subs->set_language (dcp::LanguageTag("en-US"));
1407 subs->add (simple_subtitle());
1408 subs->write (path / "subs2.mxf");
1409 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1410 cpl->reels()[1]->add(reel_subs);
1414 dcp::String::compose("libdcp %1", dcp::version),
1415 dcp::String::compose("libdcp %1", dcp::version),
1416 dcp::LocalTime().as_string(),
1420 check_verify_result (
1423 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1425 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1430 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1432 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1433 auto constexpr reel_length = 192;
1434 auto dcp = make_simple (path, 2, reel_length);
1435 auto cpl = dcp->cpls()[0];
1438 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1439 ccaps->set_language (dcp::LanguageTag("de-DE"));
1440 ccaps->add (simple_subtitle());
1441 ccaps->write (path / "subs1.mxf");
1442 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1443 cpl->reels()[0]->add(reel_ccaps);
1447 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1448 ccaps->set_language (dcp::LanguageTag("en-US"));
1449 ccaps->add (simple_subtitle());
1450 ccaps->write (path / "subs2.mxf");
1451 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1452 cpl->reels()[1]->add(reel_ccaps);
1456 dcp::String::compose("libdcp %1", dcp::version),
1457 dcp::String::compose("libdcp %1", dcp::version),
1458 dcp::LocalTime().as_string(),
1462 check_verify_result (
1465 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1466 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1471 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1473 path dir = "build/test/verify_missing_subtitle_start_time";
1474 prepare_directory (dir);
1475 auto dcp = make_simple (dir, 1, 106);
1478 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1479 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1480 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1481 "<ContentTitleText>Content</ContentTitleText>"
1482 "<AnnotationText>Annotation</AnnotationText>"
1483 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1484 "<ReelNumber>1</ReelNumber>"
1485 "<Language>de-DE</Language>"
1486 "<EditRate>24 1</EditRate>"
1487 "<TimeCodeRate>24</TimeCodeRate>"
1488 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1490 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1491 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1492 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1498 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1499 BOOST_REQUIRE (xml_file);
1500 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1502 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1503 subs->write (dir / "subs.mxf");
1505 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1506 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1508 dcp::String::compose("libdcp %1", dcp::version),
1509 dcp::String::compose("libdcp %1", dcp::version),
1510 dcp::LocalTime().as_string(),
1514 check_verify_result (
1517 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1518 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1523 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1525 path dir = "build/test/verify_invalid_subtitle_start_time";
1526 prepare_directory (dir);
1527 auto dcp = make_simple (dir, 1, 106);
1530 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1531 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1532 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1533 "<ContentTitleText>Content</ContentTitleText>"
1534 "<AnnotationText>Annotation</AnnotationText>"
1535 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1536 "<ReelNumber>1</ReelNumber>"
1537 "<Language>de-DE</Language>"
1538 "<EditRate>24 1</EditRate>"
1539 "<TimeCodeRate>24</TimeCodeRate>"
1540 "<StartTime>00:00:02:00</StartTime>"
1541 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1543 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1544 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1545 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1551 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1552 BOOST_REQUIRE (xml_file);
1553 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1555 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1556 subs->write (dir / "subs.mxf");
1558 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1559 dcp->cpls().front()->reels().front()->add(reel_subs);
1561 dcp::String::compose("libdcp %1", dcp::version),
1562 dcp::String::compose("libdcp %1", dcp::version),
1563 dcp::LocalTime().as_string(),
1567 check_verify_result (
1570 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1571 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1579 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1582 , v_position(v_position_)
1594 shared_ptr<dcp::CPL>
1595 dcp_with_text (path dir, vector<TestText> subs)
1597 prepare_directory (dir);
1598 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1599 asset->set_start_time (dcp::Time());
1600 for (auto i: subs) {
1601 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1603 asset->set_language (dcp::LanguageTag("de-DE"));
1604 asset->write (dir / "subs.mxf");
1606 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1607 return write_dcp_with_single_asset (dir, reel_asset);
1611 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1613 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1614 /* Just too early */
1615 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1616 check_verify_result (
1619 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1620 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1626 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1628 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1629 /* Just late enough */
1630 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1631 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1635 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1637 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1638 prepare_directory (dir);
1640 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1641 asset1->set_start_time (dcp::Time());
1642 /* Just late enough */
1643 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1644 asset1->set_language (dcp::LanguageTag("de-DE"));
1645 asset1->write (dir / "subs1.mxf");
1646 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1647 auto reel1 = make_shared<dcp::Reel>();
1648 reel1->add (reel_asset1);
1649 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1650 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1651 reel1->add (markers1);
1653 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1654 asset2->set_start_time (dcp::Time());
1655 /* This would be too early on first reel but should be OK on the second */
1656 add_test_subtitle (asset2, 3, 4 * 24);
1657 asset2->set_language (dcp::LanguageTag("de-DE"));
1658 asset2->write (dir / "subs2.mxf");
1659 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1660 auto reel2 = make_shared<dcp::Reel>();
1661 reel2->add (reel_asset2);
1662 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1663 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1664 reel2->add (markers2);
1666 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1669 auto dcp = make_shared<dcp::DCP>(dir);
1672 dcp::String::compose("libdcp %1", dcp::version),
1673 dcp::String::compose("libdcp %1", dcp::version),
1674 dcp::LocalTime().as_string(),
1679 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1683 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1685 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1686 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1690 { 5 * 24 + 1, 6 * 24 },
1692 check_verify_result (
1695 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1696 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1701 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1703 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1704 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1708 { 5 * 24 + 16, 8 * 24 },
1710 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1714 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1716 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1717 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1718 check_verify_result (
1721 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1727 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1729 auto const dir = path("build/test/verify_valid_subtitle_duration");
1730 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1731 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1735 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1737 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1738 prepare_directory (dir);
1739 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1740 asset->set_start_time (dcp::Time());
1741 add_test_subtitle (asset, 0, 4 * 24);
1742 asset->set_language (dcp::LanguageTag("de-DE"));
1743 asset->write (dir / "subs.mxf");
1745 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1746 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1751 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1752 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1753 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1759 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1761 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1762 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1765 { 96, 200, 0.0, "We" },
1766 { 96, 200, 0.1, "have" },
1767 { 96, 200, 0.2, "four" },
1768 { 96, 200, 0.3, "lines" }
1770 check_verify_result (
1773 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1779 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1781 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1782 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1785 { 96, 200, 0.0, "We" },
1786 { 96, 200, 0.1, "have" },
1787 { 96, 200, 0.2, "four" },
1789 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1793 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1795 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1796 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1799 { 96, 300, 0.0, "We" },
1800 { 96, 300, 0.1, "have" },
1801 { 150, 180, 0.2, "four" },
1802 { 150, 180, 0.3, "lines" }
1804 check_verify_result (
1807 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1808 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1813 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1815 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1816 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1819 { 96, 300, 0.0, "We" },
1820 { 96, 300, 0.1, "have" },
1821 { 150, 180, 0.2, "four" },
1822 { 190, 250, 0.3, "lines" }
1824 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1828 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1830 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1831 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1834 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1836 check_verify_result (
1839 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1840 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1845 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1847 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1848 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1851 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1853 check_verify_result (
1856 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1862 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1864 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1865 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1868 { 96, 200, 0.0, "We" },
1869 { 96, 200, 0.1, "have" },
1870 { 96, 200, 0.2, "four" },
1871 { 96, 200, 0.3, "lines" }
1873 check_verify_result (
1876 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1882 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1884 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1885 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1888 { 96, 200, 0.0, "We" },
1889 { 96, 200, 0.1, "have" },
1890 { 96, 200, 0.2, "four" },
1892 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1896 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1898 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1899 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1902 { 96, 300, 0.0, "We" },
1903 { 96, 300, 0.1, "have" },
1904 { 150, 180, 0.2, "four" },
1905 { 150, 180, 0.3, "lines" }
1907 check_verify_result (
1910 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1916 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1918 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1919 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1922 { 96, 300, 0.0, "We" },
1923 { 96, 300, 0.1, "have" },
1924 { 150, 180, 0.2, "four" },
1925 { 190, 250, 0.3, "lines" }
1927 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1931 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1933 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1934 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1937 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1939 check_verify_result (
1942 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1943 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1948 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1950 path const dir("build/test/verify_invalid_sound_frame_rate");
1951 prepare_directory (dir);
1953 auto picture = simple_picture (dir, "foo");
1954 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1955 auto reel = make_shared<dcp::Reel>();
1956 reel->add (reel_picture);
1957 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1958 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1959 reel->add (reel_sound);
1960 reel->add (simple_markers());
1961 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1963 auto dcp = make_shared<dcp::DCP>(dir);
1966 dcp::String::compose("libdcp %1", dcp::version),
1967 dcp::String::compose("libdcp %1", dcp::version),
1968 dcp::LocalTime().as_string(),
1972 check_verify_result (
1975 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1976 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1981 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1983 path const dir("build/test/verify_missing_cpl_annotation_text");
1984 auto dcp = make_simple (dir);
1986 dcp::String::compose("libdcp %1", dcp::version),
1987 dcp::String::compose("libdcp %1", dcp::version),
1988 dcp::LocalTime().as_string(),
1992 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1994 auto const cpl = dcp->cpls()[0];
1997 BOOST_REQUIRE (cpl->file());
1998 Editor e(cpl->file().get());
1999 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2002 check_verify_result (
2005 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2006 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2011 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2013 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2014 auto dcp = make_simple (dir);
2016 dcp::String::compose("libdcp %1", dcp::version),
2017 dcp::String::compose("libdcp %1", dcp::version),
2018 dcp::LocalTime().as_string(),
2022 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2023 auto const cpl = dcp->cpls()[0];
2026 BOOST_REQUIRE (cpl->file());
2027 Editor e(cpl->file().get());
2028 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2031 check_verify_result (
2034 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2035 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2040 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2042 path const dir("build/test/verify_mismatched_asset_duration");
2043 prepare_directory (dir);
2044 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2045 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2047 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2048 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2050 auto reel = make_shared<dcp::Reel>(
2051 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2052 make_shared<dcp::ReelSoundAsset>(ms, 0)
2055 reel->add (simple_markers());
2060 dcp::String::compose("libdcp %1", dcp::version),
2061 dcp::String::compose("libdcp %1", dcp::version),
2062 dcp::LocalTime().as_string(),
2066 check_verify_result (
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2070 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2077 shared_ptr<dcp::CPL>
2078 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2080 prepare_directory (dir);
2081 auto dcp = make_shared<dcp::DCP>(dir);
2082 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2084 auto constexpr reel_length = 192;
2086 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2087 subs->set_language (dcp::LanguageTag("de-DE"));
2088 subs->set_start_time (dcp::Time());
2089 subs->add (simple_subtitle());
2090 subs->write (dir / "subs.mxf");
2091 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2093 auto reel1 = make_shared<dcp::Reel>(
2094 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2095 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2099 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2102 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2103 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2104 reel1->add (markers1);
2108 auto reel2 = make_shared<dcp::Reel>(
2109 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2110 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2114 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2117 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2118 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2119 reel2->add (markers2);
2125 dcp::String::compose("libdcp %1", dcp::version),
2126 dcp::String::compose("libdcp %1", dcp::version),
2127 dcp::LocalTime().as_string(),
2135 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2138 path dir ("build/test/missing_main_subtitle_from_some_reels");
2139 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2140 check_verify_result (
2143 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2144 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2150 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2151 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2152 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2156 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2157 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2158 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2164 shared_ptr<dcp::CPL>
2165 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2167 prepare_directory (dir);
2168 auto dcp = make_shared<dcp::DCP>(dir);
2169 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2171 auto constexpr reel_length = 192;
2173 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2174 subs->set_language (dcp::LanguageTag("de-DE"));
2175 subs->set_start_time (dcp::Time());
2176 subs->add (simple_subtitle());
2177 subs->write (dir / "subs.mxf");
2179 auto reel1 = make_shared<dcp::Reel>(
2180 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2181 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2184 for (int i = 0; i < caps_in_reel1; ++i) {
2185 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2188 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2189 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2190 reel1->add (markers1);
2194 auto reel2 = make_shared<dcp::Reel>(
2195 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2196 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2199 for (int i = 0; i < caps_in_reel2; ++i) {
2200 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2203 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2204 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2205 reel2->add (markers2);
2211 dcp::String::compose("libdcp %1", dcp::version),
2212 dcp::String::compose("libdcp %1", dcp::version),
2213 dcp::LocalTime().as_string(),
2221 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2224 path dir ("build/test/mismatched_closed_caption_asset_counts");
2225 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2226 check_verify_result (
2229 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2235 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2236 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2237 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2241 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2242 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2243 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2250 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2252 prepare_directory (dir);
2253 auto dcp = make_shared<dcp::DCP>(dir);
2254 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2256 auto constexpr reel_length = 192;
2258 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2259 subs->set_language (dcp::LanguageTag("de-DE"));
2260 subs->set_start_time (dcp::Time());
2261 subs->add (simple_subtitle());
2262 subs->write (dir / "subs.mxf");
2263 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2266 auto reel = make_shared<dcp::Reel>(
2267 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2268 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2271 reel->add (reel_text);
2273 reel->add (simple_markers(reel_length));
2279 dcp::String::compose("libdcp %1", dcp::version),
2280 dcp::String::compose("libdcp %1", dcp::version),
2281 dcp::LocalTime().as_string(),
2285 check_verify_result (
2288 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2289 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2294 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2296 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2297 "build/test/verify_subtitle_entry_point_must_be_present",
2298 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2299 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2300 asset->unset_entry_point ();
2304 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2305 "build/test/verify_subtitle_entry_point_must_be_zero",
2306 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2307 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2308 asset->set_entry_point (4);
2312 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2313 "build/test/verify_closed_caption_entry_point_must_be_present",
2314 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2315 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2316 asset->unset_entry_point ();
2320 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2321 "build/test/verify_closed_caption_entry_point_must_be_zero",
2322 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2323 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2324 asset->set_entry_point (9);
2330 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2334 path const dir("build/test/verify_missing_hash");
2335 auto dcp = make_simple (dir);
2337 dcp::String::compose("libdcp %1", dcp::version),
2338 dcp::String::compose("libdcp %1", dcp::version),
2339 dcp::LocalTime().as_string(),
2343 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2344 auto const cpl = dcp->cpls()[0];
2345 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2346 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2347 auto asset_id = cpl->reels()[0]->main_picture()->id();
2350 BOOST_REQUIRE (cpl->file());
2351 Editor e(cpl->file().get());
2352 e.delete_first_line_containing("<Hash>");
2355 check_verify_result (
2358 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2359 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2366 verify_markers_test (
2368 vector<pair<dcp::Marker, dcp::Time>> markers,
2369 vector<dcp::VerificationNote> test_notes
2372 auto dcp = make_simple (dir);
2373 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2374 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2375 for (auto const& i: markers) {
2376 markers_asset->set (i.first, i.second);
2378 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2380 dcp::String::compose("libdcp %1", dcp::version),
2381 dcp::String::compose("libdcp %1", dcp::version),
2382 dcp::LocalTime().as_string(),
2386 check_verify_result ({dir}, test_notes);
2390 BOOST_AUTO_TEST_CASE (verify_markers)
2392 verify_markers_test (
2393 "build/test/verify_markers_all_correct",
2395 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2396 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2397 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2398 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2403 verify_markers_test (
2404 "build/test/verify_markers_missing_ffec",
2406 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2407 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2408 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2411 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2414 verify_markers_test (
2415 "build/test/verify_markers_missing_ffmc",
2417 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2418 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2419 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2422 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2425 verify_markers_test (
2426 "build/test/verify_markers_missing_ffoc",
2428 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2429 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2430 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2433 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2436 verify_markers_test (
2437 "build/test/verify_markers_missing_lfoc",
2439 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2440 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2441 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2444 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2447 verify_markers_test (
2448 "build/test/verify_markers_incorrect_ffoc",
2450 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2451 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2452 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2453 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2456 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2459 verify_markers_test (
2460 "build/test/verify_markers_incorrect_lfoc",
2462 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2463 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2464 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2465 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2468 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2473 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2475 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2476 prepare_directory (dir);
2477 auto dcp = make_simple (dir);
2478 auto cpl = dcp->cpls()[0];
2479 cpl->unset_version_number();
2481 dcp::String::compose("libdcp %1", dcp::version),
2482 dcp::String::compose("libdcp %1", dcp::version),
2483 dcp::LocalTime().as_string(),
2487 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2491 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2493 path dir = "build/test/verify_missing_extension_metadata1";
2494 auto dcp = make_simple (dir);
2496 dcp::String::compose("libdcp %1", dcp::version),
2497 dcp::String::compose("libdcp %1", dcp::version),
2498 dcp::LocalTime().as_string(),
2502 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2503 auto cpl = dcp->cpls()[0];
2506 Editor e (cpl->file().get());
2507 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2510 check_verify_result (
2513 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2514 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2519 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2521 path dir = "build/test/verify_missing_extension_metadata2";
2522 auto dcp = make_simple (dir);
2524 dcp::String::compose("libdcp %1", dcp::version),
2525 dcp::String::compose("libdcp %1", dcp::version),
2526 dcp::LocalTime().as_string(),
2530 auto cpl = dcp->cpls()[0];
2533 Editor e (cpl->file().get());
2534 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2537 check_verify_result (
2540 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2541 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2546 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2548 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2549 auto dcp = make_simple (dir);
2551 dcp::String::compose("libdcp %1", dcp::version),
2552 dcp::String::compose("libdcp %1", dcp::version),
2553 dcp::LocalTime().as_string(),
2557 auto const cpl = dcp->cpls()[0];
2560 Editor e (cpl->file().get());
2561 e.replace ("<meta:Name>A", "<meta:NameX>A");
2562 e.replace ("n</meta:Name>", "n</meta:NameX>");
2565 check_verify_result (
2568 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2569 { 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 },
2570 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2575 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2577 path dir = "build/test/verify_invalid_extension_metadata1";
2578 auto dcp = make_simple (dir);
2580 dcp::String::compose("libdcp %1", dcp::version),
2581 dcp::String::compose("libdcp %1", dcp::version),
2582 dcp::LocalTime().as_string(),
2586 auto cpl = dcp->cpls()[0];
2589 Editor e (cpl->file().get());
2590 e.replace ("Application", "Fred");
2593 check_verify_result (
2596 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2602 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2604 path dir = "build/test/verify_invalid_extension_metadata2";
2605 auto dcp = make_simple (dir);
2607 dcp::String::compose("libdcp %1", dcp::version),
2608 dcp::String::compose("libdcp %1", dcp::version),
2609 dcp::LocalTime().as_string(),
2613 auto cpl = dcp->cpls()[0];
2616 Editor e (cpl->file().get());
2617 e.replace ("DCP Constraints Profile", "Fred");
2620 check_verify_result (
2623 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2624 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2629 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2631 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2632 auto dcp = make_simple (dir);
2634 dcp::String::compose("libdcp %1", dcp::version),
2635 dcp::String::compose("libdcp %1", dcp::version),
2636 dcp::LocalTime().as_string(),
2640 auto const cpl = dcp->cpls()[0];
2643 Editor e (cpl->file().get());
2644 e.replace ("<meta:Value>", "<meta:ValueX>");
2645 e.replace ("</meta:Value>", "</meta:ValueX>");
2648 check_verify_result (
2651 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2652 { 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 },
2653 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2658 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2660 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2661 auto dcp = make_simple (dir);
2663 dcp::String::compose("libdcp %1", dcp::version),
2664 dcp::String::compose("libdcp %1", dcp::version),
2665 dcp::LocalTime().as_string(),
2669 auto const cpl = dcp->cpls()[0];
2672 Editor e (cpl->file().get());
2673 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2676 check_verify_result (
2679 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2680 { 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() },
2685 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2687 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2688 auto dcp = make_simple (dir);
2690 dcp::String::compose("libdcp %1", dcp::version),
2691 dcp::String::compose("libdcp %1", dcp::version),
2692 dcp::LocalTime().as_string(),
2696 auto const cpl = dcp->cpls()[0];
2699 Editor e (cpl->file().get());
2700 e.replace ("<meta:Property>", "<meta:PropertyX>");
2701 e.replace ("</meta:Property>", "</meta:PropertyX>");
2704 check_verify_result (
2707 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2708 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2709 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2714 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2716 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2717 auto dcp = make_simple (dir);
2719 dcp::String::compose("libdcp %1", dcp::version),
2720 dcp::String::compose("libdcp %1", dcp::version),
2721 dcp::LocalTime().as_string(),
2725 auto const cpl = dcp->cpls()[0];
2728 Editor e (cpl->file().get());
2729 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2730 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2733 check_verify_result (
2736 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2737 { 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 },
2738 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2744 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2746 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2747 prepare_directory (dir);
2748 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2749 copy_file (i.path(), dir / i.path().filename());
2752 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2753 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2757 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2760 check_verify_result (
2763 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2764 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2765 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2766 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2767 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2768 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2770 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2775 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2777 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2778 prepare_directory (dir);
2779 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2780 copy_file (i.path(), dir / i.path().filename());
2783 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2784 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2787 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2790 check_verify_result (
2793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2794 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2795 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2796 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2797 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2799 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2804 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2806 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2807 prepare_directory (dir);
2808 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2809 copy_file (i.path(), dir / i.path().filename());
2813 Editor e (dir / dcp_test1_pkl);
2814 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2817 check_verify_result ({dir}, {});
2821 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2823 path dir ("build/test/verify_must_not_be_partially_encrypted");
2824 prepare_directory (dir);
2828 auto signer = make_shared<dcp::CertificateChain>();
2829 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2830 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2831 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2832 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2834 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2838 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2841 auto writer = mp->start_write (dir / "video.mxf", false);
2842 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2843 for (int i = 0; i < 24; ++i) {
2844 writer->write (j2c.data(), j2c.size());
2846 writer->finalize ();
2848 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2850 auto reel = make_shared<dcp::Reel>(
2851 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2852 make_shared<dcp::ReelSoundAsset>(ms, 0)
2855 reel->add (simple_markers());
2859 cpl->set_content_version (
2860 {"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"}
2862 cpl->set_annotation_text ("A Test DCP");
2863 cpl->set_issuer ("OpenDCP 0.0.25");
2864 cpl->set_creator ("OpenDCP 0.0.25");
2865 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2866 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2867 cpl->set_main_sound_sample_rate (48000);
2868 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2869 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2870 cpl->set_version_number (1);
2874 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2876 check_verify_result (
2879 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2884 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2886 vector<dcp::VerificationNote> notes;
2887 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"));
2888 auto reader = picture.start_read ();
2889 auto frame = reader->get_frame (0);
2890 verify_j2k (frame, notes);
2891 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2895 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2897 vector<dcp::VerificationNote> notes;
2898 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2899 auto reader = picture.start_read ();
2900 auto frame = reader->get_frame (0);
2901 verify_j2k (frame, notes);
2902 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2906 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2908 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2909 prepare_directory (dir);
2910 auto dcp = make_simple (dir);
2912 vector<dcp::VerificationNote> notes;
2913 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2914 auto reader = picture.start_read ();
2915 auto frame = reader->get_frame (0);
2916 verify_j2k (frame, notes);
2917 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2921 /** Check that ResourceID and the XML ID being different is spotted */
2922 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2924 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2925 prepare_directory (dir);
2927 ASDCP::WriterInfo writer_info;
2928 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2931 auto mxf_id = dcp::make_uuid ();
2932 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2933 BOOST_REQUIRE (c == Kumu::UUID_Length);
2935 auto resource_id = dcp::make_uuid ();
2936 ASDCP::TimedText::TimedTextDescriptor descriptor;
2937 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2938 DCP_ASSERT (c == Kumu::UUID_Length);
2940 auto xml_id = dcp::make_uuid ();
2941 ASDCP::TimedText::MXFWriter writer;
2942 auto subs_mxf = dir / "subs.mxf";
2943 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2944 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2945 writer.WriteTimedTextResource (dcp::String::compose(
2946 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2947 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2948 "<Id>urn:uuid:%1</Id>"
2949 "<ContentTitleText>Content</ContentTitleText>"
2950 "<AnnotationText>Annotation</AnnotationText>"
2951 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2952 "<ReelNumber>1</ReelNumber>"
2953 "<Language>en-US</Language>"
2954 "<EditRate>25 1</EditRate>"
2955 "<TimeCodeRate>25</TimeCodeRate>"
2956 "<StartTime>00:00:00:00</StartTime>"
2958 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2959 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2960 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2969 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2970 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2972 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2974 check_verify_result (
2977 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2979 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2985 /** Check that ResourceID and the MXF ID being the same is spotted */
2986 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2988 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2989 prepare_directory (dir);
2991 ASDCP::WriterInfo writer_info;
2992 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2995 auto mxf_id = dcp::make_uuid ();
2996 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2997 BOOST_REQUIRE (c == Kumu::UUID_Length);
2999 auto resource_id = mxf_id;
3000 ASDCP::TimedText::TimedTextDescriptor descriptor;
3001 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3002 DCP_ASSERT (c == Kumu::UUID_Length);
3004 auto xml_id = resource_id;
3005 ASDCP::TimedText::MXFWriter writer;
3006 auto subs_mxf = dir / "subs.mxf";
3007 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3008 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3009 writer.WriteTimedTextResource (dcp::String::compose(
3010 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3011 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3012 "<Id>urn:uuid:%1</Id>"
3013 "<ContentTitleText>Content</ContentTitleText>"
3014 "<AnnotationText>Annotation</AnnotationText>"
3015 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3016 "<ReelNumber>1</ReelNumber>"
3017 "<Language>en-US</Language>"
3018 "<EditRate>25 1</EditRate>"
3019 "<TimeCodeRate>25</TimeCodeRate>"
3020 "<StartTime>00:00:00:00</StartTime>"
3022 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3023 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3024 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3033 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3034 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3036 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3038 check_verify_result (
3041 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3042 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3043 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3044 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3049 /** Check a DCP with a 3D asset marked as 2D */
3050 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3052 check_verify_result (
3053 { private_test / "data" / "xm" },
3056 dcp::VerificationNote::Type::WARNING,
3057 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3060 dcp::VerificationNote::Type::BV21_ERROR,
3061 dcp::VerificationNote::Code::INVALID_STANDARD