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_EQUAL (notes[i], 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 'xD9HpNAjKsECGtJEWtwV2/T5ndG8=' 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 'xz/y4fI+ocWzVB7cr8pVt2OYpOHA=' 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_external_asset)
765 path const ov_dir("build/test/verify_external_asset");
766 prepare_directory (ov_dir);
768 auto image = black_image ();
769 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
770 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
771 dcp_from_frame (frame, ov_dir);
773 dcp::DCP ov (ov_dir);
776 path const vf_dir("build/test/verify_external_asset_vf");
777 prepare_directory (vf_dir);
779 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
780 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
782 check_verify_result (
785 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
791 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
793 path const dir("build/test/verify_valid_cpl_metadata");
794 prepare_directory (dir);
796 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
797 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
798 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
800 auto reel = make_shared<dcp::Reel>();
801 reel->add (reel_asset);
803 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
804 reel->add (simple_markers(16 * 24));
806 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
808 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
809 cpl->set_main_sound_sample_rate (48000);
810 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
811 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
812 cpl->set_version_number (1);
817 dcp::String::compose("libdcp %1", dcp::version),
818 dcp::String::compose("libdcp %1", dcp::version),
819 dcp::LocalTime().as_string(),
825 path find_cpl (path dir)
827 for (auto i: directory_iterator(dir)) {
828 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
833 BOOST_REQUIRE (false);
838 /* DCP with invalid CompositionMetadataAsset */
839 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
841 using namespace boost::filesystem;
843 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
844 prepare_directory (dir);
846 auto reel = make_shared<dcp::Reel>();
847 reel->add (black_picture_asset(dir));
848 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
850 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
851 cpl->set_main_sound_sample_rate (48000);
852 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
853 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
854 cpl->set_version_number (1);
856 reel->add (simple_markers());
861 dcp::String::compose("libdcp %1", dcp::version),
862 dcp::String::compose("libdcp %1", dcp::version),
863 dcp::LocalTime().as_string(),
868 Editor e (find_cpl(dir));
869 e.replace ("MainSound", "MainSoundX");
872 check_verify_result (
875 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
876 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
878 dcp::VerificationNote::Type::ERROR,
879 dcp::VerificationNote::Code::INVALID_XML,
880 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
881 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
882 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
883 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
884 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
885 "ExtensionMetadataList?,)'"),
886 canonical(cpl->file().get()),
889 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
894 /* DCP with invalid CompositionMetadataAsset */
895 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
897 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
898 prepare_directory (dir);
900 auto reel = make_shared<dcp::Reel>();
901 reel->add (black_picture_asset(dir));
902 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
904 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
905 cpl->set_main_sound_sample_rate (48000);
906 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
907 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
912 dcp::String::compose("libdcp %1", dcp::version),
913 dcp::String::compose("libdcp %1", dcp::version),
914 dcp::LocalTime().as_string(),
919 Editor e (find_cpl(dir));
920 e.replace ("meta:Width", "meta:WidthX");
923 check_verify_result (
925 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
930 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
932 path const dir("build/test/verify_invalid_language1");
933 prepare_directory (dir);
934 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
935 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
936 asset->_language = "wrong-andbad";
937 asset->write (dir / "subs.mxf");
938 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
939 reel_asset->_language = "badlang";
940 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
942 check_verify_result (
945 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
946 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
952 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
953 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
955 path const dir("build/test/verify_invalid_language2");
956 prepare_directory (dir);
957 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
958 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
959 asset->_language = "wrong-andbad";
960 asset->write (dir / "subs.mxf");
961 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
962 reel_asset->_language = "badlang";
963 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
965 check_verify_result (
968 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
969 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
970 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
975 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
976 * the release territory.
978 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
980 path const dir("build/test/verify_invalid_language3");
981 prepare_directory (dir);
983 auto picture = simple_picture (dir, "foo");
984 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
985 auto reel = make_shared<dcp::Reel>();
986 reel->add (reel_picture);
987 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
988 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
989 reel->add (reel_sound);
990 reel->add (simple_markers());
992 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
994 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
995 cpl->_additional_subtitle_languages.push_back("andso-is-this");
996 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
997 cpl->set_main_sound_sample_rate (48000);
998 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
999 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1000 cpl->set_version_number (1);
1001 cpl->_release_territory = "fred-jim";
1002 auto dcp = make_shared<dcp::DCP>(dir);
1005 dcp::String::compose("libdcp %1", dcp::version),
1006 dcp::String::compose("libdcp %1", dcp::version),
1007 dcp::LocalTime().as_string(),
1011 check_verify_result (
1014 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1015 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1016 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1017 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1023 vector<dcp::VerificationNote>
1024 check_picture_size (int width, int height, int frame_rate, bool three_d)
1026 using namespace boost::filesystem;
1028 path dcp_path = "build/test/verify_picture_test";
1029 prepare_directory (dcp_path);
1031 shared_ptr<dcp::PictureAsset> mp;
1033 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1035 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1037 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1039 auto image = black_image (dcp::Size(width, height));
1040 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1041 int const length = three_d ? frame_rate * 2 : frame_rate;
1042 for (int i = 0; i < length; ++i) {
1043 picture_writer->write (j2c.data(), j2c.size());
1045 picture_writer->finalize ();
1047 auto d = make_shared<dcp::DCP>(dcp_path);
1048 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1049 cpl->set_annotation_text ("A Test DCP");
1050 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1051 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1052 cpl->set_main_sound_sample_rate (48000);
1053 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1054 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1055 cpl->set_version_number (1);
1057 auto reel = make_shared<dcp::Reel>();
1060 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1062 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1065 reel->add (simple_markers(frame_rate));
1071 dcp::String::compose("libdcp %1", dcp::version),
1072 dcp::String::compose("libdcp %1", dcp::version),
1073 dcp::LocalTime().as_string(),
1077 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1083 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1085 auto notes = check_picture_size(width, height, frame_rate, three_d);
1086 BOOST_CHECK_EQUAL (notes.size(), 0U);
1092 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1094 auto notes = check_picture_size(width, height, frame_rate, three_d);
1095 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1096 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1097 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1103 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1105 auto notes = check_picture_size(width, height, frame_rate, three_d);
1106 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1107 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1108 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1114 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1116 auto notes = check_picture_size(width, height, frame_rate, three_d);
1117 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1118 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1119 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1123 BOOST_AUTO_TEST_CASE (verify_picture_size)
1125 using namespace boost::filesystem;
1128 check_picture_size_ok (2048, 858, 24, false);
1129 check_picture_size_ok (2048, 858, 25, false);
1130 check_picture_size_ok (2048, 858, 48, false);
1131 check_picture_size_ok (2048, 858, 24, true);
1132 check_picture_size_ok (2048, 858, 25, true);
1133 check_picture_size_ok (2048, 858, 48, true);
1136 check_picture_size_ok (1998, 1080, 24, false);
1137 check_picture_size_ok (1998, 1080, 25, false);
1138 check_picture_size_ok (1998, 1080, 48, false);
1139 check_picture_size_ok (1998, 1080, 24, true);
1140 check_picture_size_ok (1998, 1080, 25, true);
1141 check_picture_size_ok (1998, 1080, 48, true);
1144 check_picture_size_ok (4096, 1716, 24, false);
1147 check_picture_size_ok (3996, 2160, 24, false);
1149 /* Bad frame size */
1150 check_picture_size_bad_frame_size (2050, 858, 24, false);
1151 check_picture_size_bad_frame_size (2048, 658, 25, false);
1152 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1153 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1155 /* Bad 2K frame rate */
1156 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1157 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1158 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1160 /* Bad 4K frame rate */
1161 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1162 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1165 auto notes = check_picture_size(3996, 2160, 24, true);
1166 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1167 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1168 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1174 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1177 make_shared<dcp::SubtitleString>(
1185 dcp::Time(start_frame, 24, 24),
1186 dcp::Time(end_frame, 24, 24),
1188 dcp::HAlign::CENTER,
1190 dcp::VAlign::CENTER,
1191 dcp::Direction::LTR,
1202 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1204 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1205 prepare_directory (dir);
1207 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1208 for (int i = 0; i < 2048; ++i) {
1209 add_test_subtitle (asset, i * 24, i * 24 + 20);
1211 asset->set_language (dcp::LanguageTag("de-DE"));
1212 asset->write (dir / "subs.mxf");
1213 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1214 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1216 check_verify_result (
1219 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1221 dcp::VerificationNote::Type::BV21_ERROR,
1222 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1224 canonical(dir / "subs.mxf")
1226 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1227 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1233 shared_ptr<dcp::SMPTESubtitleAsset>
1234 make_large_subtitle_asset (path font_file)
1236 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1237 dcp::ArrayData big_fake_font(1024 * 1024);
1238 big_fake_font.write (font_file);
1239 for (int i = 0; i < 116; ++i) {
1240 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1248 verify_timed_text_asset_too_large (string name)
1250 auto const dir = path("build/test") / name;
1251 prepare_directory (dir);
1252 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1253 add_test_subtitle (asset, 0, 240);
1254 asset->set_language (dcp::LanguageTag("de-DE"));
1255 asset->write (dir / "subs.mxf");
1257 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1258 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1260 check_verify_result (
1263 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1264 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1266 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1267 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1272 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1274 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1275 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1279 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1281 path dir = "build/test/verify_missing_subtitle_language";
1282 prepare_directory (dir);
1283 auto dcp = make_simple (dir, 1, 106);
1286 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1287 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1288 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1289 "<ContentTitleText>Content</ContentTitleText>"
1290 "<AnnotationText>Annotation</AnnotationText>"
1291 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1292 "<ReelNumber>1</ReelNumber>"
1293 "<EditRate>24 1</EditRate>"
1294 "<TimeCodeRate>24</TimeCodeRate>"
1295 "<StartTime>00:00:00:00</StartTime>"
1296 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1298 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1299 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1300 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1306 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1307 BOOST_REQUIRE (xml_file);
1308 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1310 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1311 subs->write (dir / "subs.mxf");
1313 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1314 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1316 dcp::String::compose("libdcp %1", dcp::version),
1317 dcp::String::compose("libdcp %1", dcp::version),
1318 dcp::LocalTime().as_string(),
1322 check_verify_result (
1325 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1326 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1331 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1333 path path ("build/test/verify_mismatched_subtitle_languages");
1334 auto constexpr reel_length = 192;
1335 auto dcp = make_simple (path, 2, reel_length);
1336 auto cpl = dcp->cpls()[0];
1339 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1340 subs->set_language (dcp::LanguageTag("de-DE"));
1341 subs->add (simple_subtitle());
1342 subs->write (path / "subs1.mxf");
1343 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1344 cpl->reels()[0]->add(reel_subs);
1348 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1349 subs->set_language (dcp::LanguageTag("en-US"));
1350 subs->add (simple_subtitle());
1351 subs->write (path / "subs2.mxf");
1352 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1353 cpl->reels()[1]->add(reel_subs);
1357 dcp::String::compose("libdcp %1", dcp::version),
1358 dcp::String::compose("libdcp %1", dcp::version),
1359 dcp::LocalTime().as_string(),
1363 check_verify_result (
1366 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1367 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1368 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1373 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1375 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1376 auto constexpr reel_length = 192;
1377 auto dcp = make_simple (path, 2, reel_length);
1378 auto cpl = dcp->cpls()[0];
1381 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1382 ccaps->set_language (dcp::LanguageTag("de-DE"));
1383 ccaps->add (simple_subtitle());
1384 ccaps->write (path / "subs1.mxf");
1385 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1386 cpl->reels()[0]->add(reel_ccaps);
1390 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1391 ccaps->set_language (dcp::LanguageTag("en-US"));
1392 ccaps->add (simple_subtitle());
1393 ccaps->write (path / "subs2.mxf");
1394 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1395 cpl->reels()[1]->add(reel_ccaps);
1399 dcp::String::compose("libdcp %1", dcp::version),
1400 dcp::String::compose("libdcp %1", dcp::version),
1401 dcp::LocalTime().as_string(),
1405 check_verify_result (
1408 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1409 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1414 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1416 path dir = "build/test/verify_missing_subtitle_start_time";
1417 prepare_directory (dir);
1418 auto dcp = make_simple (dir, 1, 106);
1421 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1422 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1423 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1424 "<ContentTitleText>Content</ContentTitleText>"
1425 "<AnnotationText>Annotation</AnnotationText>"
1426 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1427 "<ReelNumber>1</ReelNumber>"
1428 "<Language>de-DE</Language>"
1429 "<EditRate>24 1</EditRate>"
1430 "<TimeCodeRate>24</TimeCodeRate>"
1431 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1433 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1434 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1435 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1441 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1442 BOOST_REQUIRE (xml_file);
1443 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1445 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1446 subs->write (dir / "subs.mxf");
1448 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1449 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1451 dcp::String::compose("libdcp %1", dcp::version),
1452 dcp::String::compose("libdcp %1", dcp::version),
1453 dcp::LocalTime().as_string(),
1457 check_verify_result (
1460 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1461 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1466 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1468 path dir = "build/test/verify_invalid_subtitle_start_time";
1469 prepare_directory (dir);
1470 auto dcp = make_simple (dir, 1, 106);
1473 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1474 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1475 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1476 "<ContentTitleText>Content</ContentTitleText>"
1477 "<AnnotationText>Annotation</AnnotationText>"
1478 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1479 "<ReelNumber>1</ReelNumber>"
1480 "<Language>de-DE</Language>"
1481 "<EditRate>24 1</EditRate>"
1482 "<TimeCodeRate>24</TimeCodeRate>"
1483 "<StartTime>00:00:02:00</StartTime>"
1484 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1486 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1487 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1488 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1494 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1495 BOOST_REQUIRE (xml_file);
1496 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1498 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1499 subs->write (dir / "subs.mxf");
1501 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1502 dcp->cpls().front()->reels().front()->add(reel_subs);
1504 dcp::String::compose("libdcp %1", dcp::version),
1505 dcp::String::compose("libdcp %1", dcp::version),
1506 dcp::LocalTime().as_string(),
1510 check_verify_result (
1513 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1514 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1522 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1525 , v_position(v_position_)
1537 shared_ptr<dcp::CPL>
1538 dcp_with_text (path dir, vector<TestText> subs)
1540 prepare_directory (dir);
1541 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1542 asset->set_start_time (dcp::Time());
1543 for (auto i: subs) {
1544 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1546 asset->set_language (dcp::LanguageTag("de-DE"));
1547 asset->write (dir / "subs.mxf");
1549 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1550 return write_dcp_with_single_asset (dir, reel_asset);
1554 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1556 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1557 /* Just too early */
1558 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1559 check_verify_result (
1562 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1563 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1569 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1571 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1572 /* Just late enough */
1573 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1574 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1578 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1580 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1581 prepare_directory (dir);
1583 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1584 asset1->set_start_time (dcp::Time());
1585 /* Just late enough */
1586 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1587 asset1->set_language (dcp::LanguageTag("de-DE"));
1588 asset1->write (dir / "subs1.mxf");
1589 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1590 auto reel1 = make_shared<dcp::Reel>();
1591 reel1->add (reel_asset1);
1592 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1593 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1594 reel1->add (markers1);
1596 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1597 asset2->set_start_time (dcp::Time());
1598 /* This would be too early on first reel but should be OK on the second */
1599 add_test_subtitle (asset2, 3, 4 * 24);
1600 asset2->set_language (dcp::LanguageTag("de-DE"));
1601 asset2->write (dir / "subs2.mxf");
1602 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1603 auto reel2 = make_shared<dcp::Reel>();
1604 reel2->add (reel_asset2);
1605 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1606 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1607 reel2->add (markers2);
1609 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1612 auto dcp = make_shared<dcp::DCP>(dir);
1615 dcp::String::compose("libdcp %1", dcp::version),
1616 dcp::String::compose("libdcp %1", dcp::version),
1617 dcp::LocalTime().as_string(),
1622 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1626 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1628 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1629 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1633 { 5 * 24 + 1, 6 * 24 },
1635 check_verify_result (
1638 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1639 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1644 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1646 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1647 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1651 { 5 * 24 + 16, 8 * 24 },
1653 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1657 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1659 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1660 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1661 check_verify_result (
1664 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1665 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1670 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1672 auto const dir = path("build/test/verify_valid_subtitle_duration");
1673 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1674 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1678 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1680 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1681 prepare_directory (dir);
1682 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1683 asset->set_start_time (dcp::Time());
1684 add_test_subtitle (asset, 0, 4 * 24);
1685 asset->set_language (dcp::LanguageTag("de-DE"));
1686 asset->write (dir / "subs.mxf");
1688 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1689 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1690 check_verify_result (
1693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1695 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1696 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1702 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1704 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1705 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1708 { 96, 200, 0.0, "We" },
1709 { 96, 200, 0.1, "have" },
1710 { 96, 200, 0.2, "four" },
1711 { 96, 200, 0.3, "lines" }
1713 check_verify_result (
1716 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1722 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1724 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1725 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1728 { 96, 200, 0.0, "We" },
1729 { 96, 200, 0.1, "have" },
1730 { 96, 200, 0.2, "four" },
1732 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1736 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1738 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1739 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1742 { 96, 300, 0.0, "We" },
1743 { 96, 300, 0.1, "have" },
1744 { 150, 180, 0.2, "four" },
1745 { 150, 180, 0.3, "lines" }
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1756 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1758 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1759 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1762 { 96, 300, 0.0, "We" },
1763 { 96, 300, 0.1, "have" },
1764 { 150, 180, 0.2, "four" },
1765 { 190, 250, 0.3, "lines" }
1767 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1771 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1773 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1774 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1777 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1779 check_verify_result (
1782 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1783 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1788 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1790 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1791 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1794 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1796 check_verify_result (
1799 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1805 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1807 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1808 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1811 { 96, 200, 0.0, "We" },
1812 { 96, 200, 0.1, "have" },
1813 { 96, 200, 0.2, "four" },
1814 { 96, 200, 0.3, "lines" }
1816 check_verify_result (
1819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1820 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1825 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1827 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1828 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1831 { 96, 200, 0.0, "We" },
1832 { 96, 200, 0.1, "have" },
1833 { 96, 200, 0.2, "four" },
1835 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1839 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1841 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1842 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1845 { 96, 300, 0.0, "We" },
1846 { 96, 300, 0.1, "have" },
1847 { 150, 180, 0.2, "four" },
1848 { 150, 180, 0.3, "lines" }
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1859 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1861 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1862 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1865 { 96, 300, 0.0, "We" },
1866 { 96, 300, 0.1, "have" },
1867 { 150, 180, 0.2, "four" },
1868 { 190, 250, 0.3, "lines" }
1870 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1874 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1876 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1877 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1880 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1882 check_verify_result (
1885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1891 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1893 path const dir("build/test/verify_invalid_sound_frame_rate");
1894 prepare_directory (dir);
1896 auto picture = simple_picture (dir, "foo");
1897 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1898 auto reel = make_shared<dcp::Reel>();
1899 reel->add (reel_picture);
1900 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1901 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1902 reel->add (reel_sound);
1903 reel->add (simple_markers());
1904 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1906 auto dcp = make_shared<dcp::DCP>(dir);
1909 dcp::String::compose("libdcp %1", dcp::version),
1910 dcp::String::compose("libdcp %1", dcp::version),
1911 dcp::LocalTime().as_string(),
1915 check_verify_result (
1918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1924 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1926 path const dir("build/test/verify_missing_cpl_annotation_text");
1927 auto dcp = make_simple (dir);
1929 dcp::String::compose("libdcp %1", dcp::version),
1930 dcp::String::compose("libdcp %1", dcp::version),
1931 dcp::LocalTime().as_string(),
1935 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1937 auto const cpl = dcp->cpls()[0];
1940 BOOST_REQUIRE (cpl->file());
1941 Editor e(cpl->file().get());
1942 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1945 check_verify_result (
1948 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1949 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1954 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1956 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1957 auto dcp = make_simple (dir);
1959 dcp::String::compose("libdcp %1", dcp::version),
1960 dcp::String::compose("libdcp %1", dcp::version),
1961 dcp::LocalTime().as_string(),
1965 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1966 auto const cpl = dcp->cpls()[0];
1969 BOOST_REQUIRE (cpl->file());
1970 Editor e(cpl->file().get());
1971 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1974 check_verify_result (
1977 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1978 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1983 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1985 path const dir("build/test/verify_mismatched_asset_duration");
1986 prepare_directory (dir);
1987 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1988 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1990 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1991 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1993 auto reel = make_shared<dcp::Reel>(
1994 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1995 make_shared<dcp::ReelSoundAsset>(ms, 0)
1998 reel->add (simple_markers());
2003 dcp::String::compose("libdcp %1", dcp::version),
2004 dcp::String::compose("libdcp %1", dcp::version),
2005 dcp::LocalTime().as_string(),
2009 check_verify_result (
2012 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2013 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2020 shared_ptr<dcp::CPL>
2021 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2023 prepare_directory (dir);
2024 auto dcp = make_shared<dcp::DCP>(dir);
2025 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2027 auto constexpr reel_length = 192;
2029 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2030 subs->set_language (dcp::LanguageTag("de-DE"));
2031 subs->set_start_time (dcp::Time());
2032 subs->add (simple_subtitle());
2033 subs->write (dir / "subs.mxf");
2034 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2036 auto reel1 = make_shared<dcp::Reel>(
2037 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2038 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2042 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2045 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2046 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2047 reel1->add (markers1);
2051 auto reel2 = make_shared<dcp::Reel>(
2052 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2053 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2057 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2060 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2061 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2062 reel2->add (markers2);
2068 dcp::String::compose("libdcp %1", dcp::version),
2069 dcp::String::compose("libdcp %1", dcp::version),
2070 dcp::LocalTime().as_string(),
2078 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2081 path dir ("build/test/missing_main_subtitle_from_some_reels");
2082 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2083 check_verify_result (
2086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2093 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2094 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2095 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2099 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2100 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2101 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2107 shared_ptr<dcp::CPL>
2108 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2110 prepare_directory (dir);
2111 auto dcp = make_shared<dcp::DCP>(dir);
2112 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2114 auto constexpr reel_length = 192;
2116 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2117 subs->set_language (dcp::LanguageTag("de-DE"));
2118 subs->set_start_time (dcp::Time());
2119 subs->add (simple_subtitle());
2120 subs->write (dir / "subs.mxf");
2122 auto reel1 = make_shared<dcp::Reel>(
2123 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2124 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2127 for (int i = 0; i < caps_in_reel1; ++i) {
2128 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2131 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2132 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2133 reel1->add (markers1);
2137 auto reel2 = make_shared<dcp::Reel>(
2138 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2139 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2142 for (int i = 0; i < caps_in_reel2; ++i) {
2143 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2146 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2147 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2148 reel2->add (markers2);
2154 dcp::String::compose("libdcp %1", dcp::version),
2155 dcp::String::compose("libdcp %1", dcp::version),
2156 dcp::LocalTime().as_string(),
2164 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2167 path dir ("build/test/mismatched_closed_caption_asset_counts");
2168 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2169 check_verify_result (
2172 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2173 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2178 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2179 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2180 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2184 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2185 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2186 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2193 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2195 prepare_directory (dir);
2196 auto dcp = make_shared<dcp::DCP>(dir);
2197 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2199 auto constexpr reel_length = 192;
2201 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2202 subs->set_language (dcp::LanguageTag("de-DE"));
2203 subs->set_start_time (dcp::Time());
2204 subs->add (simple_subtitle());
2205 subs->write (dir / "subs.mxf");
2206 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2209 auto reel = make_shared<dcp::Reel>(
2210 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2211 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2214 reel->add (reel_text);
2216 reel->add (simple_markers(reel_length));
2222 dcp::String::compose("libdcp %1", dcp::version),
2223 dcp::String::compose("libdcp %1", dcp::version),
2224 dcp::LocalTime().as_string(),
2228 check_verify_result (
2231 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2237 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2239 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2240 "build/test/verify_subtitle_entry_point_must_be_present",
2241 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2242 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2243 asset->unset_entry_point ();
2247 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2248 "build/test/verify_subtitle_entry_point_must_be_zero",
2249 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2250 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2251 asset->set_entry_point (4);
2255 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2256 "build/test/verify_closed_caption_entry_point_must_be_present",
2257 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2258 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2259 asset->unset_entry_point ();
2263 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2264 "build/test/verify_closed_caption_entry_point_must_be_zero",
2265 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2266 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2267 asset->set_entry_point (9);
2273 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2277 path const dir("build/test/verify_missing_hash");
2278 auto dcp = make_simple (dir);
2280 dcp::String::compose("libdcp %1", dcp::version),
2281 dcp::String::compose("libdcp %1", dcp::version),
2282 dcp::LocalTime().as_string(),
2286 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2287 auto const cpl = dcp->cpls()[0];
2288 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2289 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2290 auto asset_id = cpl->reels()[0]->main_picture()->id();
2293 BOOST_REQUIRE (cpl->file());
2294 Editor e(cpl->file().get());
2295 e.delete_first_line_containing("<Hash>");
2298 check_verify_result (
2301 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2302 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2309 verify_markers_test (
2311 vector<pair<dcp::Marker, dcp::Time>> markers,
2312 vector<dcp::VerificationNote> test_notes
2315 auto dcp = make_simple (dir);
2316 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2317 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2318 for (auto const& i: markers) {
2319 markers_asset->set (i.first, i.second);
2321 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2323 dcp::String::compose("libdcp %1", dcp::version),
2324 dcp::String::compose("libdcp %1", dcp::version),
2325 dcp::LocalTime().as_string(),
2329 check_verify_result ({dir}, test_notes);
2333 BOOST_AUTO_TEST_CASE (verify_markers)
2335 verify_markers_test (
2336 "build/test/verify_markers_all_correct",
2338 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2339 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2340 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2341 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2346 verify_markers_test (
2347 "build/test/verify_markers_missing_ffec",
2349 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2350 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2351 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2354 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2357 verify_markers_test (
2358 "build/test/verify_markers_missing_ffmc",
2360 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2361 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2362 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2365 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2368 verify_markers_test (
2369 "build/test/verify_markers_missing_ffoc",
2371 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2372 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2373 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2376 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2379 verify_markers_test (
2380 "build/test/verify_markers_missing_lfoc",
2382 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2383 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2384 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2387 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2390 verify_markers_test (
2391 "build/test/verify_markers_incorrect_ffoc",
2393 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2394 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2395 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2396 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2399 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2402 verify_markers_test (
2403 "build/test/verify_markers_incorrect_lfoc",
2405 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2406 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2407 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2408 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2411 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2416 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2418 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2419 prepare_directory (dir);
2420 auto dcp = make_simple (dir);
2421 auto cpl = dcp->cpls()[0];
2422 cpl->unset_version_number();
2424 dcp::String::compose("libdcp %1", dcp::version),
2425 dcp::String::compose("libdcp %1", dcp::version),
2426 dcp::LocalTime().as_string(),
2430 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2434 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2436 path dir = "build/test/verify_missing_extension_metadata1";
2437 auto dcp = make_simple (dir);
2439 dcp::String::compose("libdcp %1", dcp::version),
2440 dcp::String::compose("libdcp %1", dcp::version),
2441 dcp::LocalTime().as_string(),
2445 auto cpl = dcp->cpls()[0];
2448 Editor e (cpl->file().get());
2449 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2452 check_verify_result (
2455 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2456 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2461 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2463 path dir = "build/test/verify_missing_extension_metadata2";
2464 auto dcp = make_simple (dir);
2466 dcp::String::compose("libdcp %1", dcp::version),
2467 dcp::String::compose("libdcp %1", dcp::version),
2468 dcp::LocalTime().as_string(),
2472 auto cpl = dcp->cpls()[0];
2475 Editor e (cpl->file().get());
2476 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2479 check_verify_result (
2482 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2483 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2488 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2490 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2491 auto dcp = make_simple (dir);
2493 dcp::String::compose("libdcp %1", dcp::version),
2494 dcp::String::compose("libdcp %1", dcp::version),
2495 dcp::LocalTime().as_string(),
2499 auto const cpl = dcp->cpls()[0];
2502 Editor e (cpl->file().get());
2503 e.replace ("<meta:Name>A", "<meta:NameX>A");
2504 e.replace ("n</meta:Name>", "n</meta:NameX>");
2507 check_verify_result (
2510 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2511 { 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 },
2512 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2517 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2519 path dir = "build/test/verify_invalid_extension_metadata1";
2520 auto dcp = make_simple (dir);
2522 dcp::String::compose("libdcp %1", dcp::version),
2523 dcp::String::compose("libdcp %1", dcp::version),
2524 dcp::LocalTime().as_string(),
2528 auto cpl = dcp->cpls()[0];
2531 Editor e (cpl->file().get());
2532 e.replace ("Application", "Fred");
2535 check_verify_result (
2538 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2539 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2544 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2546 path dir = "build/test/verify_invalid_extension_metadata2";
2547 auto dcp = make_simple (dir);
2549 dcp::String::compose("libdcp %1", dcp::version),
2550 dcp::String::compose("libdcp %1", dcp::version),
2551 dcp::LocalTime().as_string(),
2555 auto cpl = dcp->cpls()[0];
2558 Editor e (cpl->file().get());
2559 e.replace ("DCP Constraints Profile", "Fred");
2562 check_verify_result (
2565 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2566 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2571 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2573 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2574 auto dcp = make_simple (dir);
2576 dcp::String::compose("libdcp %1", dcp::version),
2577 dcp::String::compose("libdcp %1", dcp::version),
2578 dcp::LocalTime().as_string(),
2582 auto const cpl = dcp->cpls()[0];
2585 Editor e (cpl->file().get());
2586 e.replace ("<meta:Value>", "<meta:ValueX>");
2587 e.replace ("</meta:Value>", "</meta:ValueX>");
2590 check_verify_result (
2593 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2594 { 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 },
2595 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2600 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2602 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2603 auto dcp = make_simple (dir);
2605 dcp::String::compose("libdcp %1", dcp::version),
2606 dcp::String::compose("libdcp %1", dcp::version),
2607 dcp::LocalTime().as_string(),
2611 auto const cpl = dcp->cpls()[0];
2614 Editor e (cpl->file().get());
2615 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2618 check_verify_result (
2621 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2622 { 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() },
2627 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2629 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2630 auto dcp = make_simple (dir);
2632 dcp::String::compose("libdcp %1", dcp::version),
2633 dcp::String::compose("libdcp %1", dcp::version),
2634 dcp::LocalTime().as_string(),
2638 auto const cpl = dcp->cpls()[0];
2641 Editor e (cpl->file().get());
2642 e.replace ("<meta:Property>", "<meta:PropertyX>");
2643 e.replace ("</meta:Property>", "</meta:PropertyX>");
2646 check_verify_result (
2649 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2650 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2651 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2656 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2658 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2659 auto dcp = make_simple (dir);
2661 dcp::String::compose("libdcp %1", dcp::version),
2662 dcp::String::compose("libdcp %1", dcp::version),
2663 dcp::LocalTime().as_string(),
2667 auto const cpl = dcp->cpls()[0];
2670 Editor e (cpl->file().get());
2671 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2672 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2675 check_verify_result (
2678 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2679 { 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 },
2680 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2686 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2688 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2689 prepare_directory (dir);
2690 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2691 copy_file (i.path(), dir / i.path().filename());
2694 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2695 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2699 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2702 check_verify_result (
2705 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2707 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2708 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2709 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2710 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2712 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2717 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2719 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2720 prepare_directory (dir);
2721 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2722 copy_file (i.path(), dir / i.path().filename());
2725 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2726 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2729 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2732 check_verify_result (
2735 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2736 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2737 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2738 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2739 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2740 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2741 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2746 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2748 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2749 prepare_directory (dir);
2750 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2751 copy_file (i.path(), dir / i.path().filename());
2755 Editor e (dir / dcp_test1_pkl);
2756 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2759 check_verify_result ({dir}, {});
2763 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2765 path dir ("build/test/verify_must_not_be_partially_encrypted");
2766 prepare_directory (dir);
2770 auto signer = make_shared<dcp::CertificateChain>();
2771 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2772 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2773 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2774 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2776 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2780 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2783 auto writer = mp->start_write (dir / "video.mxf", false);
2784 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2785 for (int i = 0; i < 24; ++i) {
2786 writer->write (j2c.data(), j2c.size());
2788 writer->finalize ();
2790 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2792 auto reel = make_shared<dcp::Reel>(
2793 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2794 make_shared<dcp::ReelSoundAsset>(ms, 0)
2797 reel->add (simple_markers());
2801 cpl->set_content_version (
2802 {"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"}
2804 cpl->set_annotation_text ("A Test DCP");
2805 cpl->set_issuer ("OpenDCP 0.0.25");
2806 cpl->set_creator ("OpenDCP 0.0.25");
2807 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2808 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2809 cpl->set_main_sound_sample_rate (48000);
2810 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2811 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2812 cpl->set_version_number (1);
2816 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2818 check_verify_result (
2821 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2826 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2828 vector<dcp::VerificationNote> notes;
2829 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"));
2830 auto reader = picture.start_read ();
2831 auto frame = reader->get_frame (0);
2832 verify_j2k (frame, notes);
2833 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2837 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2839 vector<dcp::VerificationNote> notes;
2840 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2841 auto reader = picture.start_read ();
2842 auto frame = reader->get_frame (0);
2843 verify_j2k (frame, notes);
2844 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2848 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2850 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2851 prepare_directory (dir);
2852 auto dcp = make_simple (dir);
2854 vector<dcp::VerificationNote> notes;
2855 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2856 auto reader = picture.start_read ();
2857 auto frame = reader->get_frame (0);
2858 verify_j2k (frame, notes);
2859 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2863 /** Check that ResourceID and the XML ID being different is spotted */
2864 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2866 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2867 prepare_directory (dir);
2869 ASDCP::WriterInfo writer_info;
2870 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2873 auto mxf_id = dcp::make_uuid ();
2874 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2875 BOOST_REQUIRE (c == Kumu::UUID_Length);
2877 auto resource_id = dcp::make_uuid ();
2878 ASDCP::TimedText::TimedTextDescriptor descriptor;
2879 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2880 DCP_ASSERT (c == Kumu::UUID_Length);
2882 auto xml_id = dcp::make_uuid ();
2883 ASDCP::TimedText::MXFWriter writer;
2884 auto subs_mxf = dir / "subs.mxf";
2885 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2886 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2887 writer.WriteTimedTextResource (dcp::String::compose(
2888 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2889 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2890 "<Id>urn:uuid:%1</Id>"
2891 "<ContentTitleText>Content</ContentTitleText>"
2892 "<AnnotationText>Annotation</AnnotationText>"
2893 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2894 "<ReelNumber>1</ReelNumber>"
2895 "<Language>en-US</Language>"
2896 "<EditRate>25 1</EditRate>"
2897 "<TimeCodeRate>25</TimeCodeRate>"
2898 "<StartTime>00:00:00:00</StartTime>"
2900 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2901 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2902 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2911 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2912 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2914 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2916 check_verify_result (
2919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2920 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2921 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2922 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2927 /** Check that ResourceID and the MXF ID being the same is spotted */
2928 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2930 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2931 prepare_directory (dir);
2933 ASDCP::WriterInfo writer_info;
2934 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2937 auto mxf_id = dcp::make_uuid ();
2938 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2939 BOOST_REQUIRE (c == Kumu::UUID_Length);
2941 auto resource_id = mxf_id;
2942 ASDCP::TimedText::TimedTextDescriptor descriptor;
2943 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2944 DCP_ASSERT (c == Kumu::UUID_Length);
2946 auto xml_id = resource_id;
2947 ASDCP::TimedText::MXFWriter writer;
2948 auto subs_mxf = dir / "subs.mxf";
2949 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2950 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2951 writer.WriteTimedTextResource (dcp::String::compose(
2952 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2953 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2954 "<Id>urn:uuid:%1</Id>"
2955 "<ContentTitleText>Content</ContentTitleText>"
2956 "<AnnotationText>Annotation</AnnotationText>"
2957 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2958 "<ReelNumber>1</ReelNumber>"
2959 "<Language>en-US</Language>"
2960 "<EditRate>25 1</EditRate>"
2961 "<TimeCodeRate>25</TimeCodeRate>"
2962 "<StartTime>00:00:00:00</StartTime>"
2964 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2965 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2966 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2975 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2976 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2978 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2980 check_verify_result (
2983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2985 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2991 /** Check a DCP with a 3D asset marked as 2D */
2992 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
2994 check_verify_result (
2995 { private_test / "data" / "xm" },
2998 dcp::VerificationNote::Type::WARNING,
2999 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3002 dcp::VerificationNote::Type::BV21_ERROR,
3003 dcp::VerificationNote::Code::INVALID_STANDARD