2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_interop_closed_caption_asset.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_closed_caption_asset.h"
51 #include "reel_smpte_subtitle_asset.h"
52 #include "smpte_subtitle_asset.h"
53 #include "stereo_picture_asset.h"
54 #include "stream_operators.h"
58 #include "verify_j2k.h"
59 #include <boost/test/unit_test.hpp>
60 #include <boost/algorithm/string.hpp>
70 using std::make_shared;
71 using boost::optional;
72 using namespace boost::filesystem;
73 using std::shared_ptr;
76 static list<pair<string, optional<path>>> stages;
78 static string filename_to_id(boost::filesystem::path path)
80 return path.string().substr(4, path.string().length() - 8);
83 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
84 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
86 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
87 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
89 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
91 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
92 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
94 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
95 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
98 stage (string s, optional<path> p)
100 stages.push_back (make_pair (s, p));
110 prepare_directory (path path)
112 using namespace boost::filesystem;
114 create_directories (path);
119 setup (int reference_number, string verify_test_suffix)
121 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
122 prepare_directory (dir);
123 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
124 copy_file (i.path(), dir / i.path().filename());
133 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
135 auto reel = make_shared<dcp::Reel>();
136 reel->add (reel_asset);
137 reel->add (simple_markers());
139 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
141 auto dcp = make_shared<dcp::DCP>(dir);
144 dcp::String::compose("libdcp %1", dcp::version),
145 dcp::String::compose("libdcp %1", dcp::version),
146 dcp::LocalTime().as_string(),
154 /** Class that can alter a file by searching and replacing strings within it.
155 * On destruction modifies the file whose name was given to the constructor.
163 _content = dcp::file_to_string (_path);
168 auto f = fopen(_path.string().c_str(), "w");
170 fwrite (_content.c_str(), _content.length(), 1, f);
174 void replace (string a, string b)
176 auto old_content = _content;
177 boost::algorithm::replace_all (_content, a, b);
178 BOOST_REQUIRE (_content != old_content);
181 void delete_first_line_containing (string s)
183 vector<string> lines;
184 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
185 auto old_content = _content;
188 for (auto i: lines) {
189 if (i.find(s) == string::npos || done) {
190 _content += i + "\n";
195 BOOST_REQUIRE (_content != old_content);
198 void delete_lines (string from, string to)
200 vector<string> lines;
201 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
202 bool deleting = false;
203 auto old_content = _content;
205 for (auto i: lines) {
206 if (i.find(from) != string::npos) {
210 _content += i + "\n";
212 if (deleting && i.find(to) != string::npos) {
216 BOOST_REQUIRE (_content != old_content);
221 std::string _content;
225 LIBDCP_DISABLE_WARNINGS
228 dump_notes (vector<dcp::VerificationNote> const & notes)
230 for (auto i: notes) {
231 std::cout << dcp::note_to_string(i) << "\n";
234 LIBDCP_ENABLE_WARNINGS
239 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
241 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
242 std::sort (notes.begin(), notes.end());
243 std::sort (test_notes.begin(), test_notes.end());
245 string message = "\nVerification notes from test:\n";
246 for (auto i: notes) {
247 message += " " + note_to_string(i) + "\n";
249 message += "Expected:\n";
250 for (auto i: test_notes) {
251 message += " " + note_to_string(i) + "\n";
254 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
260 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
262 auto dir = setup (1, suffix);
265 Editor e (file(suffix));
266 e.replace (from, to);
269 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
271 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
272 auto i = notes.begin();
273 auto j = codes.begin();
274 while (i != notes.end()) {
275 BOOST_CHECK_EQUAL (i->code(), *j);
282 BOOST_AUTO_TEST_CASE (verify_no_error)
285 auto dir = setup (1, "no_error");
286 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
288 path const cpl_file = dir / dcp_test1_cpl;
289 path const pkl_file = dir / dcp_test1_pkl;
290 path const assetmap_file = dir / "ASSETMAP.xml";
292 auto st = stages.begin();
293 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
294 BOOST_REQUIRE (st->second);
295 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
297 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
298 BOOST_REQUIRE (st->second);
299 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
301 BOOST_CHECK_EQUAL (st->first, "Checking reel");
302 BOOST_REQUIRE (!st->second);
304 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
305 BOOST_REQUIRE (st->second);
306 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
308 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
309 BOOST_REQUIRE (st->second);
310 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
312 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
313 BOOST_REQUIRE (st->second);
314 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
316 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
317 BOOST_REQUIRE (st->second);
318 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
320 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
321 BOOST_REQUIRE (st->second);
322 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
324 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
325 BOOST_REQUIRE (st->second);
326 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
328 BOOST_REQUIRE (st == stages.end());
330 BOOST_CHECK_EQUAL (notes.size(), 0);
334 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
336 using namespace boost::filesystem;
338 auto dir = setup (1, "incorrect_picture_sound_hash");
340 auto video_path = path(dir / "video.mxf");
341 auto mod = fopen(video_path.string().c_str(), "r+b");
343 fseek (mod, 4096, SEEK_SET);
345 fwrite (&x, sizeof(x), 1, mod);
348 auto audio_path = path(dir / "audio.mxf");
349 mod = fopen(audio_path.string().c_str(), "r+b");
351 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
352 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
355 dcp::ASDCPErrorSuspender sus;
356 check_verify_result (
359 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
360 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
365 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
367 using namespace boost::filesystem;
369 auto dir = setup (1, "mismatched_picture_sound_hashes");
372 Editor e (dir / dcp_test1_pkl);
373 e.replace ("<Hash>", "<Hash>x");
376 check_verify_result (
379 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
380 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
381 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
382 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xLq7ot/GobgrqUYdlbR8FCD5APqs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
383 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xgVKhC9IkWyzQbgzpFcJ1bpqbtwk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
384 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xc1DRq6GaSzV2brF0YnSNed46nqk=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 }
389 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
391 auto dir = setup (1, "failed_read_content_kind");
394 Editor e (dir / dcp_test1_cpl);
395 e.replace ("<ContentKind>", "<ContentKind>x");
398 check_verify_result (
400 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
409 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
417 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
423 asset_map (string suffix)
425 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
429 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
431 check_verify_result_after_replace (
432 "invalid_picture_frame_rate", &cpl,
433 "<FrameRate>24 1", "<FrameRate>99 1",
434 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
435 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
439 BOOST_AUTO_TEST_CASE (verify_missing_asset)
441 auto dir = setup (1, "missing_asset");
442 remove (dir / "video.mxf");
443 check_verify_result (
446 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
451 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
453 check_verify_result_after_replace (
454 "empty_asset_path", &asset_map,
455 "<Path>video.mxf</Path>", "<Path></Path>",
456 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
461 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
463 check_verify_result_after_replace (
464 "mismatched_standard", &cpl,
465 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
466 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
467 dcp::VerificationNote::Code::INVALID_XML,
468 dcp::VerificationNote::Code::INVALID_XML,
469 dcp::VerificationNote::Code::INVALID_XML,
470 dcp::VerificationNote::Code::INVALID_XML,
471 dcp::VerificationNote::Code::INVALID_XML,
472 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
477 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
479 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
480 check_verify_result_after_replace (
481 "invalid_xml_cpl_id", &cpl,
482 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
483 { dcp::VerificationNote::Code::INVALID_XML }
488 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
490 check_verify_result_after_replace (
491 "invalid_xml_issue_date", &cpl,
492 "<IssueDate>", "<IssueDate>x",
493 { dcp::VerificationNote::Code::INVALID_XML,
494 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
499 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
501 check_verify_result_after_replace (
502 "invalid_xml_pkl_id", &pkl,
503 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
504 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
505 { dcp::VerificationNote::Code::INVALID_XML }
510 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
512 check_verify_result_after_replace (
513 "invalix_xml_asset_map_id", &asset_map,
514 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
515 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
516 { dcp::VerificationNote::Code::INVALID_XML }
521 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
524 auto dir = setup (3, "verify_invalid_standard");
525 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
527 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
528 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
529 path const assetmap_file = dir / "ASSETMAP";
531 auto st = stages.begin();
532 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
533 BOOST_REQUIRE (st->second);
534 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
536 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
537 BOOST_REQUIRE (st->second);
538 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
540 BOOST_CHECK_EQUAL (st->first, "Checking reel");
541 BOOST_REQUIRE (!st->second);
543 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
544 BOOST_REQUIRE (st->second);
545 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
547 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
548 BOOST_REQUIRE (st->second);
549 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
551 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
552 BOOST_REQUIRE (st->second);
553 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
555 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
556 BOOST_REQUIRE (st->second);
557 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
559 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
560 BOOST_REQUIRE (st->second);
561 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
563 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
564 BOOST_REQUIRE (st->second);
565 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
567 BOOST_REQUIRE (st == stages.end());
569 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
570 auto i = notes.begin ();
571 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
572 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
574 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
575 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
578 /* DCP with a short asset */
579 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
581 auto dir = setup (8, "invalid_duration");
582 check_verify_result (
585 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
587 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
588 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
589 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
590 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
597 dcp_from_frame (dcp::ArrayData const& frame, path dir)
599 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
600 create_directories (dir);
601 auto writer = asset->start_write (dir / "pic.mxf", true);
602 for (int i = 0; i < 24; ++i) {
603 writer->write (frame.data(), frame.size());
607 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
608 return write_dcp_with_single_asset (dir, reel_asset);
612 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
614 int const too_big = 1302083 * 2;
616 /* Compress a black image */
617 auto image = black_image ();
618 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
619 BOOST_REQUIRE (frame.size() < too_big);
621 /* Place it in a bigger block with some zero padding at the end */
622 dcp::ArrayData oversized_frame(too_big);
623 memcpy (oversized_frame.data(), frame.data(), frame.size());
624 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
626 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
627 prepare_directory (dir);
628 auto cpl = dcp_from_frame (oversized_frame, dir);
630 check_verify_result (
633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
634 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
635 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
640 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
642 int const nearly_too_big = 1302083 * 0.98;
644 /* Compress a black image */
645 auto image = black_image ();
646 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
647 BOOST_REQUIRE (frame.size() < nearly_too_big);
649 /* Place it in a bigger block with some zero padding at the end */
650 dcp::ArrayData oversized_frame(nearly_too_big);
651 memcpy (oversized_frame.data(), frame.data(), frame.size());
652 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
654 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
655 prepare_directory (dir);
656 auto cpl = dcp_from_frame (oversized_frame, dir);
658 check_verify_result (
661 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
662 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
663 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
668 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
670 /* Compress a black image */
671 auto image = black_image ();
672 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
673 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
675 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
676 prepare_directory (dir);
677 auto cpl = dcp_from_frame (frame, dir);
679 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
683 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
685 path const dir("build/test/verify_valid_interop_subtitles");
686 prepare_directory (dir);
687 copy_file ("test/data/subs1.xml", dir / "subs.xml");
688 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
689 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
690 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
692 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
696 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
698 using namespace boost::filesystem;
700 path const dir("build/test/verify_invalid_interop_subtitles");
701 prepare_directory (dir);
702 copy_file ("test/data/subs1.xml", dir / "subs.xml");
703 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
704 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
705 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
708 Editor e (dir / "subs.xml");
709 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
712 check_verify_result (
715 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
718 dcp::VerificationNote::Type::ERROR,
719 dcp::VerificationNote::Code::INVALID_XML,
720 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
728 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
730 path const dir("build/test/verify_valid_smpte_subtitles");
731 prepare_directory (dir);
732 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
733 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
734 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
735 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
737 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
741 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
743 using namespace boost::filesystem;
745 path const dir("build/test/verify_invalid_smpte_subtitles");
746 prepare_directory (dir);
747 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
748 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
749 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
750 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
751 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
753 check_verify_result (
756 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
758 dcp::VerificationNote::Type::ERROR,
759 dcp::VerificationNote::Code::INVALID_XML,
760 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
764 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
765 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
770 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
772 path const dir("build/test/verify_empty_text_node_in_subtitles");
773 prepare_directory (dir);
774 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
775 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
776 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
777 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
779 check_verify_result (
782 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
783 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
784 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
790 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
791 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
793 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
794 prepare_directory (dir);
795 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
796 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
797 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
798 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
800 check_verify_result (
803 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
808 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
809 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
811 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
812 prepare_directory (dir);
813 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
814 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
815 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
816 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
818 check_verify_result (
821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
822 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
827 BOOST_AUTO_TEST_CASE (verify_external_asset)
829 path const ov_dir("build/test/verify_external_asset");
830 prepare_directory (ov_dir);
832 auto image = black_image ();
833 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
834 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
835 dcp_from_frame (frame, ov_dir);
837 dcp::DCP ov (ov_dir);
840 path const vf_dir("build/test/verify_external_asset_vf");
841 prepare_directory (vf_dir);
843 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
844 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
846 check_verify_result (
849 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
855 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
857 path const dir("build/test/verify_valid_cpl_metadata");
858 prepare_directory (dir);
860 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
861 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
862 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
864 auto reel = make_shared<dcp::Reel>();
865 reel->add (reel_asset);
867 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
868 reel->add (simple_markers(16 * 24));
870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
872 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873 cpl->set_main_sound_sample_rate (48000);
874 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
876 cpl->set_version_number (1);
881 dcp::String::compose("libdcp %1", dcp::version),
882 dcp::String::compose("libdcp %1", dcp::version),
883 dcp::LocalTime().as_string(),
889 path find_cpl (path dir)
891 for (auto i: directory_iterator(dir)) {
892 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
897 BOOST_REQUIRE (false);
902 /* DCP with invalid CompositionMetadataAsset */
903 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
905 using namespace boost::filesystem;
907 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
908 prepare_directory (dir);
910 auto reel = make_shared<dcp::Reel>();
911 reel->add (black_picture_asset(dir));
912 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
914 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
915 cpl->set_main_sound_sample_rate (48000);
916 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
917 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
918 cpl->set_version_number (1);
920 reel->add (simple_markers());
925 dcp::String::compose("libdcp %1", dcp::version),
926 dcp::String::compose("libdcp %1", dcp::version),
927 dcp::LocalTime().as_string(),
932 Editor e (find_cpl(dir));
933 e.replace ("MainSound", "MainSoundX");
936 check_verify_result (
939 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
940 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
942 dcp::VerificationNote::Type::ERROR,
943 dcp::VerificationNote::Code::INVALID_XML,
944 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
945 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
946 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
947 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
948 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
949 "ExtensionMetadataList?,)'"),
950 canonical(cpl->file().get()),
953 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
958 /* DCP with invalid CompositionMetadataAsset */
959 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
961 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
962 prepare_directory (dir);
964 auto reel = make_shared<dcp::Reel>();
965 reel->add (black_picture_asset(dir));
966 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
968 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
969 cpl->set_main_sound_sample_rate (48000);
970 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
971 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
976 dcp::String::compose("libdcp %1", dcp::version),
977 dcp::String::compose("libdcp %1", dcp::version),
978 dcp::LocalTime().as_string(),
983 Editor e (find_cpl(dir));
984 e.replace ("meta:Width", "meta:WidthX");
987 check_verify_result (
989 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
994 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
996 path const dir("build/test/verify_invalid_language1");
997 prepare_directory (dir);
998 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
999 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1000 asset->_language = "wrong-andbad";
1001 asset->write (dir / "subs.mxf");
1002 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1003 reel_asset->_language = "badlang";
1004 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1006 check_verify_result (
1009 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1010 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1011 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1016 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1017 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1019 path const dir("build/test/verify_invalid_language2");
1020 prepare_directory (dir);
1021 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1022 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1023 asset->_language = "wrong-andbad";
1024 asset->write (dir / "subs.mxf");
1025 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1026 reel_asset->_language = "badlang";
1027 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1029 check_verify_result (
1032 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1033 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1034 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1039 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1040 * the release territory.
1042 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1044 path const dir("build/test/verify_invalid_language3");
1045 prepare_directory (dir);
1047 auto picture = simple_picture (dir, "foo");
1048 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1049 auto reel = make_shared<dcp::Reel>();
1050 reel->add (reel_picture);
1051 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1052 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1053 reel->add (reel_sound);
1054 reel->add (simple_markers());
1056 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1058 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1059 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1060 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1061 cpl->set_main_sound_sample_rate (48000);
1062 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1063 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1064 cpl->set_version_number (1);
1065 cpl->_release_territory = "fred-jim";
1066 auto dcp = make_shared<dcp::DCP>(dir);
1069 dcp::String::compose("libdcp %1", dcp::version),
1070 dcp::String::compose("libdcp %1", dcp::version),
1071 dcp::LocalTime().as_string(),
1075 check_verify_result (
1078 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1079 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1080 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1081 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1087 vector<dcp::VerificationNote>
1088 check_picture_size (int width, int height, int frame_rate, bool three_d)
1090 using namespace boost::filesystem;
1092 path dcp_path = "build/test/verify_picture_test";
1093 prepare_directory (dcp_path);
1095 shared_ptr<dcp::PictureAsset> mp;
1097 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1099 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1101 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1103 auto image = black_image (dcp::Size(width, height));
1104 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1105 int const length = three_d ? frame_rate * 2 : frame_rate;
1106 for (int i = 0; i < length; ++i) {
1107 picture_writer->write (j2c.data(), j2c.size());
1109 picture_writer->finalize ();
1111 auto d = make_shared<dcp::DCP>(dcp_path);
1112 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1113 cpl->set_annotation_text ("A Test DCP");
1114 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1115 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1116 cpl->set_main_sound_sample_rate (48000);
1117 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1118 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1119 cpl->set_version_number (1);
1121 auto reel = make_shared<dcp::Reel>();
1124 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1126 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1129 reel->add (simple_markers(frame_rate));
1135 dcp::String::compose("libdcp %1", dcp::version),
1136 dcp::String::compose("libdcp %1", dcp::version),
1137 dcp::LocalTime().as_string(),
1141 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1147 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1149 auto notes = check_picture_size(width, height, frame_rate, three_d);
1150 BOOST_CHECK_EQUAL (notes.size(), 0U);
1156 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1158 auto notes = check_picture_size(width, height, frame_rate, three_d);
1159 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1160 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1161 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1167 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1169 auto notes = check_picture_size(width, height, frame_rate, three_d);
1170 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1171 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1172 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1178 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1180 auto notes = check_picture_size(width, height, frame_rate, three_d);
1181 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1182 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1183 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1187 BOOST_AUTO_TEST_CASE (verify_picture_size)
1189 using namespace boost::filesystem;
1192 check_picture_size_ok (2048, 858, 24, false);
1193 check_picture_size_ok (2048, 858, 25, false);
1194 check_picture_size_ok (2048, 858, 48, false);
1195 check_picture_size_ok (2048, 858, 24, true);
1196 check_picture_size_ok (2048, 858, 25, true);
1197 check_picture_size_ok (2048, 858, 48, true);
1200 check_picture_size_ok (1998, 1080, 24, false);
1201 check_picture_size_ok (1998, 1080, 25, false);
1202 check_picture_size_ok (1998, 1080, 48, false);
1203 check_picture_size_ok (1998, 1080, 24, true);
1204 check_picture_size_ok (1998, 1080, 25, true);
1205 check_picture_size_ok (1998, 1080, 48, true);
1208 check_picture_size_ok (4096, 1716, 24, false);
1211 check_picture_size_ok (3996, 2160, 24, false);
1213 /* Bad frame size */
1214 check_picture_size_bad_frame_size (2050, 858, 24, false);
1215 check_picture_size_bad_frame_size (2048, 658, 25, false);
1216 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1217 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1219 /* Bad 2K frame rate */
1220 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1221 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1222 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1224 /* Bad 4K frame rate */
1225 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1226 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1229 auto notes = check_picture_size(3996, 2160, 24, true);
1230 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1231 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1232 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1238 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1241 make_shared<dcp::SubtitleString>(
1249 dcp::Time(start_frame, 24, 24),
1250 dcp::Time(end_frame, 24, 24),
1252 dcp::HAlign::CENTER,
1254 dcp::VAlign::CENTER,
1255 dcp::Direction::LTR,
1266 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1268 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1269 prepare_directory (dir);
1271 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1272 for (int i = 0; i < 2048; ++i) {
1273 add_test_subtitle (asset, i * 24, i * 24 + 20);
1275 asset->set_language (dcp::LanguageTag("de-DE"));
1276 asset->write (dir / "subs.mxf");
1277 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1278 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1280 check_verify_result (
1283 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1285 dcp::VerificationNote::Type::BV21_ERROR,
1286 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1288 canonical(dir / "subs.mxf")
1290 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1291 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1297 shared_ptr<dcp::SMPTESubtitleAsset>
1298 make_large_subtitle_asset (path font_file)
1300 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1301 dcp::ArrayData big_fake_font(1024 * 1024);
1302 big_fake_font.write (font_file);
1303 for (int i = 0; i < 116; ++i) {
1304 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1312 verify_timed_text_asset_too_large (string name)
1314 auto const dir = path("build/test") / name;
1315 prepare_directory (dir);
1316 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1317 add_test_subtitle (asset, 0, 240);
1318 asset->set_language (dcp::LanguageTag("de-DE"));
1319 asset->write (dir / "subs.mxf");
1321 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1322 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1324 check_verify_result (
1327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695136"), canonical(dir / "subs.mxf") },
1328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1329 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1330 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1331 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1336 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1338 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1339 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1343 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1345 path dir = "build/test/verify_missing_subtitle_language";
1346 prepare_directory (dir);
1347 auto dcp = make_simple (dir, 1, 106);
1350 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1351 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1352 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1353 "<ContentTitleText>Content</ContentTitleText>"
1354 "<AnnotationText>Annotation</AnnotationText>"
1355 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1356 "<ReelNumber>1</ReelNumber>"
1357 "<EditRate>24 1</EditRate>"
1358 "<TimeCodeRate>24</TimeCodeRate>"
1359 "<StartTime>00:00:00:00</StartTime>"
1360 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1362 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1363 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1364 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1370 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1371 BOOST_REQUIRE (xml_file);
1372 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1374 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1375 subs->write (dir / "subs.mxf");
1377 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1378 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1380 dcp::String::compose("libdcp %1", dcp::version),
1381 dcp::String::compose("libdcp %1", dcp::version),
1382 dcp::LocalTime().as_string(),
1386 check_verify_result (
1389 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1390 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1395 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1397 path path ("build/test/verify_mismatched_subtitle_languages");
1398 auto constexpr reel_length = 192;
1399 auto dcp = make_simple (path, 2, reel_length);
1400 auto cpl = dcp->cpls()[0];
1403 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1404 subs->set_language (dcp::LanguageTag("de-DE"));
1405 subs->add (simple_subtitle());
1406 subs->write (path / "subs1.mxf");
1407 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1408 cpl->reels()[0]->add(reel_subs);
1412 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1413 subs->set_language (dcp::LanguageTag("en-US"));
1414 subs->add (simple_subtitle());
1415 subs->write (path / "subs2.mxf");
1416 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1417 cpl->reels()[1]->add(reel_subs);
1421 dcp::String::compose("libdcp %1", dcp::version),
1422 dcp::String::compose("libdcp %1", dcp::version),
1423 dcp::LocalTime().as_string(),
1427 check_verify_result (
1430 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1431 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1432 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1437 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1439 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1440 auto constexpr reel_length = 192;
1441 auto dcp = make_simple (path, 2, reel_length);
1442 auto cpl = dcp->cpls()[0];
1445 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1446 ccaps->set_language (dcp::LanguageTag("de-DE"));
1447 ccaps->add (simple_subtitle());
1448 ccaps->write (path / "subs1.mxf");
1449 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1450 cpl->reels()[0]->add(reel_ccaps);
1454 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1455 ccaps->set_language (dcp::LanguageTag("en-US"));
1456 ccaps->add (simple_subtitle());
1457 ccaps->write (path / "subs2.mxf");
1458 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1459 cpl->reels()[1]->add(reel_ccaps);
1463 dcp::String::compose("libdcp %1", dcp::version),
1464 dcp::String::compose("libdcp %1", dcp::version),
1465 dcp::LocalTime().as_string(),
1469 check_verify_result (
1472 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1473 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1478 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1480 path dir = "build/test/verify_missing_subtitle_start_time";
1481 prepare_directory (dir);
1482 auto dcp = make_simple (dir, 1, 106);
1485 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1486 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1487 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1488 "<ContentTitleText>Content</ContentTitleText>"
1489 "<AnnotationText>Annotation</AnnotationText>"
1490 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1491 "<ReelNumber>1</ReelNumber>"
1492 "<Language>de-DE</Language>"
1493 "<EditRate>24 1</EditRate>"
1494 "<TimeCodeRate>24</TimeCodeRate>"
1495 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1497 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1498 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1499 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1505 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1506 BOOST_REQUIRE (xml_file);
1507 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1509 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1510 subs->write (dir / "subs.mxf");
1512 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1513 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1515 dcp::String::compose("libdcp %1", dcp::version),
1516 dcp::String::compose("libdcp %1", dcp::version),
1517 dcp::LocalTime().as_string(),
1521 check_verify_result (
1524 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1525 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1530 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1532 path dir = "build/test/verify_invalid_subtitle_start_time";
1533 prepare_directory (dir);
1534 auto dcp = make_simple (dir, 1, 106);
1537 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1538 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1539 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1540 "<ContentTitleText>Content</ContentTitleText>"
1541 "<AnnotationText>Annotation</AnnotationText>"
1542 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1543 "<ReelNumber>1</ReelNumber>"
1544 "<Language>de-DE</Language>"
1545 "<EditRate>24 1</EditRate>"
1546 "<TimeCodeRate>24</TimeCodeRate>"
1547 "<StartTime>00:00:02:00</StartTime>"
1548 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1550 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1551 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1552 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1558 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1559 BOOST_REQUIRE (xml_file);
1560 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1562 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1563 subs->write (dir / "subs.mxf");
1565 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1566 dcp->cpls().front()->reels().front()->add(reel_subs);
1568 dcp::String::compose("libdcp %1", dcp::version),
1569 dcp::String::compose("libdcp %1", dcp::version),
1570 dcp::LocalTime().as_string(),
1574 check_verify_result (
1577 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1578 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1586 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1589 , v_position(v_position_)
1601 shared_ptr<dcp::CPL>
1602 dcp_with_text (path dir, vector<TestText> subs)
1604 prepare_directory (dir);
1605 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1606 asset->set_start_time (dcp::Time());
1607 for (auto i: subs) {
1608 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1610 asset->set_language (dcp::LanguageTag("de-DE"));
1611 asset->write (dir / "subs.mxf");
1613 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1614 return write_dcp_with_single_asset (dir, reel_asset);
1618 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1620 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1621 /* Just too early */
1622 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1623 check_verify_result (
1626 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1627 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1633 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1635 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1636 /* Just late enough */
1637 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1638 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1642 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1644 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1645 prepare_directory (dir);
1647 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1648 asset1->set_start_time (dcp::Time());
1649 /* Just late enough */
1650 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1651 asset1->set_language (dcp::LanguageTag("de-DE"));
1652 asset1->write (dir / "subs1.mxf");
1653 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1654 auto reel1 = make_shared<dcp::Reel>();
1655 reel1->add (reel_asset1);
1656 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1657 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1658 reel1->add (markers1);
1660 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1661 asset2->set_start_time (dcp::Time());
1662 /* This would be too early on first reel but should be OK on the second */
1663 add_test_subtitle (asset2, 3, 4 * 24);
1664 asset2->set_language (dcp::LanguageTag("de-DE"));
1665 asset2->write (dir / "subs2.mxf");
1666 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1667 auto reel2 = make_shared<dcp::Reel>();
1668 reel2->add (reel_asset2);
1669 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1670 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1671 reel2->add (markers2);
1673 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1676 auto dcp = make_shared<dcp::DCP>(dir);
1679 dcp::String::compose("libdcp %1", dcp::version),
1680 dcp::String::compose("libdcp %1", dcp::version),
1681 dcp::LocalTime().as_string(),
1686 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1690 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1692 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1693 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1697 { 5 * 24 + 1, 6 * 24 },
1699 check_verify_result (
1702 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1708 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1710 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1711 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1715 { 5 * 24 + 16, 8 * 24 },
1717 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1721 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1723 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1724 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1725 check_verify_result (
1728 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1734 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1736 auto const dir = path("build/test/verify_valid_subtitle_duration");
1737 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1738 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1742 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1744 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1745 prepare_directory (dir);
1746 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1747 asset->set_start_time (dcp::Time());
1748 add_test_subtitle (asset, 0, 4 * 24);
1749 asset->set_language (dcp::LanguageTag("de-DE"));
1750 asset->write (dir / "subs.mxf");
1752 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1753 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1754 check_verify_result (
1757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1758 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1759 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1760 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1766 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1768 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1769 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1772 { 96, 200, 0.0, "We" },
1773 { 96, 200, 0.1, "have" },
1774 { 96, 200, 0.2, "four" },
1775 { 96, 200, 0.3, "lines" }
1777 check_verify_result (
1780 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1781 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1786 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1788 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1789 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1792 { 96, 200, 0.0, "We" },
1793 { 96, 200, 0.1, "have" },
1794 { 96, 200, 0.2, "four" },
1796 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1800 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1802 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1803 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1806 { 96, 300, 0.0, "We" },
1807 { 96, 300, 0.1, "have" },
1808 { 150, 180, 0.2, "four" },
1809 { 150, 180, 0.3, "lines" }
1811 check_verify_result (
1814 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1815 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1820 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1822 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1823 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1826 { 96, 300, 0.0, "We" },
1827 { 96, 300, 0.1, "have" },
1828 { 150, 180, 0.2, "four" },
1829 { 190, 250, 0.3, "lines" }
1831 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1835 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1837 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1838 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1841 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1843 check_verify_result (
1846 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1852 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1854 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1855 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1858 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1860 check_verify_result (
1863 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1869 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1871 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1872 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1875 { 96, 200, 0.0, "We" },
1876 { 96, 200, 0.1, "have" },
1877 { 96, 200, 0.2, "four" },
1878 { 96, 200, 0.3, "lines" }
1880 check_verify_result (
1883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1889 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1891 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1892 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1895 { 96, 200, 0.0, "We" },
1896 { 96, 200, 0.1, "have" },
1897 { 96, 200, 0.2, "four" },
1899 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1903 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1905 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1906 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1909 { 96, 300, 0.0, "We" },
1910 { 96, 300, 0.1, "have" },
1911 { 150, 180, 0.2, "four" },
1912 { 150, 180, 0.3, "lines" }
1914 check_verify_result (
1917 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1923 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1925 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1926 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1929 { 96, 300, 0.0, "We" },
1930 { 96, 300, 0.1, "have" },
1931 { 150, 180, 0.2, "four" },
1932 { 190, 250, 0.3, "lines" }
1934 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1938 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1940 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1941 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1944 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1946 check_verify_result (
1949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1950 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1955 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1957 path const dir("build/test/verify_invalid_sound_frame_rate");
1958 prepare_directory (dir);
1960 auto picture = simple_picture (dir, "foo");
1961 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1962 auto reel = make_shared<dcp::Reel>();
1963 reel->add (reel_picture);
1964 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
1965 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1966 reel->add (reel_sound);
1967 reel->add (simple_markers());
1968 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1970 auto dcp = make_shared<dcp::DCP>(dir);
1973 dcp::String::compose("libdcp %1", dcp::version),
1974 dcp::String::compose("libdcp %1", dcp::version),
1975 dcp::LocalTime().as_string(),
1979 check_verify_result (
1982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1988 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1990 path const dir("build/test/verify_missing_cpl_annotation_text");
1991 auto dcp = make_simple (dir);
1993 dcp::String::compose("libdcp %1", dcp::version),
1994 dcp::String::compose("libdcp %1", dcp::version),
1995 dcp::LocalTime().as_string(),
1999 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2001 auto const cpl = dcp->cpls()[0];
2004 BOOST_REQUIRE (cpl->file());
2005 Editor e(cpl->file().get());
2006 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2009 check_verify_result (
2012 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2013 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2018 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2020 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2021 auto dcp = make_simple (dir);
2023 dcp::String::compose("libdcp %1", dcp::version),
2024 dcp::String::compose("libdcp %1", dcp::version),
2025 dcp::LocalTime().as_string(),
2029 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2030 auto const cpl = dcp->cpls()[0];
2033 BOOST_REQUIRE (cpl->file());
2034 Editor e(cpl->file().get());
2035 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2038 check_verify_result (
2041 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2042 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2047 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2049 path const dir("build/test/verify_mismatched_asset_duration");
2050 prepare_directory (dir);
2051 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2052 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2054 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2055 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2057 auto reel = make_shared<dcp::Reel>(
2058 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2059 make_shared<dcp::ReelSoundAsset>(ms, 0)
2062 reel->add (simple_markers());
2067 dcp::String::compose("libdcp %1", dcp::version),
2068 dcp::String::compose("libdcp %1", dcp::version),
2069 dcp::LocalTime().as_string(),
2073 check_verify_result (
2076 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2077 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2084 shared_ptr<dcp::CPL>
2085 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2087 prepare_directory (dir);
2088 auto dcp = make_shared<dcp::DCP>(dir);
2089 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2091 auto constexpr reel_length = 192;
2093 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2094 subs->set_language (dcp::LanguageTag("de-DE"));
2095 subs->set_start_time (dcp::Time());
2096 subs->add (simple_subtitle());
2097 subs->write (dir / "subs.mxf");
2098 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2100 auto reel1 = make_shared<dcp::Reel>(
2101 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2102 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2106 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2109 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2110 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2111 reel1->add (markers1);
2115 auto reel2 = make_shared<dcp::Reel>(
2116 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2117 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2121 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2124 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2125 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2126 reel2->add (markers2);
2132 dcp::String::compose("libdcp %1", dcp::version),
2133 dcp::String::compose("libdcp %1", dcp::version),
2134 dcp::LocalTime().as_string(),
2142 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2145 path dir ("build/test/missing_main_subtitle_from_some_reels");
2146 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2147 check_verify_result (
2150 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2151 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2157 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2158 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2159 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2163 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2164 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2165 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2171 shared_ptr<dcp::CPL>
2172 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2174 prepare_directory (dir);
2175 auto dcp = make_shared<dcp::DCP>(dir);
2176 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2178 auto constexpr reel_length = 192;
2180 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2181 subs->set_language (dcp::LanguageTag("de-DE"));
2182 subs->set_start_time (dcp::Time());
2183 subs->add (simple_subtitle());
2184 subs->write (dir / "subs.mxf");
2186 auto reel1 = make_shared<dcp::Reel>(
2187 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2188 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2191 for (int i = 0; i < caps_in_reel1; ++i) {
2192 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2195 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2196 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2197 reel1->add (markers1);
2201 auto reel2 = make_shared<dcp::Reel>(
2202 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2203 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2206 for (int i = 0; i < caps_in_reel2; ++i) {
2207 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2210 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2211 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2212 reel2->add (markers2);
2218 dcp::String::compose("libdcp %1", dcp::version),
2219 dcp::String::compose("libdcp %1", dcp::version),
2220 dcp::LocalTime().as_string(),
2228 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2231 path dir ("build/test/mismatched_closed_caption_asset_counts");
2232 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2233 check_verify_result (
2236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2237 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2242 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2243 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2244 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2248 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2249 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2250 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2257 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2259 prepare_directory (dir);
2260 auto dcp = make_shared<dcp::DCP>(dir);
2261 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2263 auto constexpr reel_length = 192;
2265 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2266 subs->set_language (dcp::LanguageTag("de-DE"));
2267 subs->set_start_time (dcp::Time());
2268 subs->add (simple_subtitle());
2269 subs->write (dir / "subs.mxf");
2270 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2273 auto reel = make_shared<dcp::Reel>(
2274 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2275 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2278 reel->add (reel_text);
2280 reel->add (simple_markers(reel_length));
2286 dcp::String::compose("libdcp %1", dcp::version),
2287 dcp::String::compose("libdcp %1", dcp::version),
2288 dcp::LocalTime().as_string(),
2292 check_verify_result (
2295 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2296 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2301 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2303 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2304 "build/test/verify_subtitle_entry_point_must_be_present",
2305 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2306 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2307 asset->unset_entry_point ();
2311 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2312 "build/test/verify_subtitle_entry_point_must_be_zero",
2313 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2314 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2315 asset->set_entry_point (4);
2319 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2320 "build/test/verify_closed_caption_entry_point_must_be_present",
2321 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2322 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2323 asset->unset_entry_point ();
2327 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2328 "build/test/verify_closed_caption_entry_point_must_be_zero",
2329 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2330 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2331 asset->set_entry_point (9);
2337 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2341 path const dir("build/test/verify_missing_hash");
2342 auto dcp = make_simple (dir);
2344 dcp::String::compose("libdcp %1", dcp::version),
2345 dcp::String::compose("libdcp %1", dcp::version),
2346 dcp::LocalTime().as_string(),
2350 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2351 auto const cpl = dcp->cpls()[0];
2352 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2353 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2354 auto asset_id = cpl->reels()[0]->main_picture()->id();
2357 BOOST_REQUIRE (cpl->file());
2358 Editor e(cpl->file().get());
2359 e.delete_first_line_containing("<Hash>");
2362 check_verify_result (
2365 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2366 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2373 verify_markers_test (
2375 vector<pair<dcp::Marker, dcp::Time>> markers,
2376 vector<dcp::VerificationNote> test_notes
2379 auto dcp = make_simple (dir);
2380 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2381 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2382 for (auto const& i: markers) {
2383 markers_asset->set (i.first, i.second);
2385 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2387 dcp::String::compose("libdcp %1", dcp::version),
2388 dcp::String::compose("libdcp %1", dcp::version),
2389 dcp::LocalTime().as_string(),
2393 check_verify_result ({dir}, test_notes);
2397 BOOST_AUTO_TEST_CASE (verify_markers)
2399 verify_markers_test (
2400 "build/test/verify_markers_all_correct",
2402 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2403 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2404 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2405 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2410 verify_markers_test (
2411 "build/test/verify_markers_missing_ffec",
2413 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2414 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2415 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2418 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2421 verify_markers_test (
2422 "build/test/verify_markers_missing_ffmc",
2424 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2425 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2426 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2429 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2432 verify_markers_test (
2433 "build/test/verify_markers_missing_ffoc",
2435 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2436 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2437 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2440 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2443 verify_markers_test (
2444 "build/test/verify_markers_missing_lfoc",
2446 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2447 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2448 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2451 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2454 verify_markers_test (
2455 "build/test/verify_markers_incorrect_ffoc",
2457 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2458 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2459 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2460 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2463 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2466 verify_markers_test (
2467 "build/test/verify_markers_incorrect_lfoc",
2469 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2470 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2471 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2472 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2475 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2480 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2482 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2483 prepare_directory (dir);
2484 auto dcp = make_simple (dir);
2485 auto cpl = dcp->cpls()[0];
2486 cpl->unset_version_number();
2488 dcp::String::compose("libdcp %1", dcp::version),
2489 dcp::String::compose("libdcp %1", dcp::version),
2490 dcp::LocalTime().as_string(),
2494 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2498 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2500 path dir = "build/test/verify_missing_extension_metadata1";
2501 auto dcp = make_simple (dir);
2503 dcp::String::compose("libdcp %1", dcp::version),
2504 dcp::String::compose("libdcp %1", dcp::version),
2505 dcp::LocalTime().as_string(),
2509 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2510 auto cpl = dcp->cpls()[0];
2513 Editor e (cpl->file().get());
2514 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2517 check_verify_result (
2520 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2521 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2526 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2528 path dir = "build/test/verify_missing_extension_metadata2";
2529 auto dcp = make_simple (dir);
2531 dcp::String::compose("libdcp %1", dcp::version),
2532 dcp::String::compose("libdcp %1", dcp::version),
2533 dcp::LocalTime().as_string(),
2537 auto cpl = dcp->cpls()[0];
2540 Editor e (cpl->file().get());
2541 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2544 check_verify_result (
2547 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2548 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2553 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2555 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2556 auto dcp = make_simple (dir);
2558 dcp::String::compose("libdcp %1", dcp::version),
2559 dcp::String::compose("libdcp %1", dcp::version),
2560 dcp::LocalTime().as_string(),
2564 auto const cpl = dcp->cpls()[0];
2567 Editor e (cpl->file().get());
2568 e.replace ("<meta:Name>A", "<meta:NameX>A");
2569 e.replace ("n</meta:Name>", "n</meta:NameX>");
2572 check_verify_result (
2575 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2576 { 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 },
2577 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2582 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2584 path dir = "build/test/verify_invalid_extension_metadata1";
2585 auto dcp = make_simple (dir);
2587 dcp::String::compose("libdcp %1", dcp::version),
2588 dcp::String::compose("libdcp %1", dcp::version),
2589 dcp::LocalTime().as_string(),
2593 auto cpl = dcp->cpls()[0];
2596 Editor e (cpl->file().get());
2597 e.replace ("Application", "Fred");
2600 check_verify_result (
2603 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2609 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2611 path dir = "build/test/verify_invalid_extension_metadata2";
2612 auto dcp = make_simple (dir);
2614 dcp::String::compose("libdcp %1", dcp::version),
2615 dcp::String::compose("libdcp %1", dcp::version),
2616 dcp::LocalTime().as_string(),
2620 auto cpl = dcp->cpls()[0];
2623 Editor e (cpl->file().get());
2624 e.replace ("DCP Constraints Profile", "Fred");
2627 check_verify_result (
2630 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2631 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2636 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2638 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2639 auto dcp = make_simple (dir);
2641 dcp::String::compose("libdcp %1", dcp::version),
2642 dcp::String::compose("libdcp %1", dcp::version),
2643 dcp::LocalTime().as_string(),
2647 auto const cpl = dcp->cpls()[0];
2650 Editor e (cpl->file().get());
2651 e.replace ("<meta:Value>", "<meta:ValueX>");
2652 e.replace ("</meta:Value>", "</meta:ValueX>");
2655 check_verify_result (
2658 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2659 { 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 },
2660 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2665 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2667 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2668 auto dcp = make_simple (dir);
2670 dcp::String::compose("libdcp %1", dcp::version),
2671 dcp::String::compose("libdcp %1", dcp::version),
2672 dcp::LocalTime().as_string(),
2676 auto const cpl = dcp->cpls()[0];
2679 Editor e (cpl->file().get());
2680 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2683 check_verify_result (
2686 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2687 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2692 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2694 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2695 auto dcp = make_simple (dir);
2697 dcp::String::compose("libdcp %1", dcp::version),
2698 dcp::String::compose("libdcp %1", dcp::version),
2699 dcp::LocalTime().as_string(),
2703 auto const cpl = dcp->cpls()[0];
2706 Editor e (cpl->file().get());
2707 e.replace ("<meta:Property>", "<meta:PropertyX>");
2708 e.replace ("</meta:Property>", "</meta:PropertyX>");
2711 check_verify_result (
2714 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2721 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2723 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2724 auto dcp = make_simple (dir);
2726 dcp::String::compose("libdcp %1", dcp::version),
2727 dcp::String::compose("libdcp %1", dcp::version),
2728 dcp::LocalTime().as_string(),
2732 auto const cpl = dcp->cpls()[0];
2735 Editor e (cpl->file().get());
2736 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2737 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2740 check_verify_result (
2743 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2744 { 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 },
2745 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2751 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2753 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2754 prepare_directory (dir);
2755 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2756 copy_file (i.path(), dir / i.path().filename());
2759 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2760 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2764 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2767 check_verify_result (
2770 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2771 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2772 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2773 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2774 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2775 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2776 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2782 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2784 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2785 prepare_directory (dir);
2786 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2787 copy_file (i.path(), dir / i.path().filename());
2790 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2791 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2794 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2797 check_verify_result (
2800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2801 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2802 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2803 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2804 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2805 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2806 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2811 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2813 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2814 prepare_directory (dir);
2815 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2816 copy_file (i.path(), dir / i.path().filename());
2820 Editor e (dir / dcp_test1_pkl);
2821 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2824 check_verify_result ({dir}, {});
2828 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2830 path dir ("build/test/verify_must_not_be_partially_encrypted");
2831 prepare_directory (dir);
2835 auto signer = make_shared<dcp::CertificateChain>();
2836 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2837 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2838 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2839 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2841 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2845 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2848 auto writer = mp->start_write (dir / "video.mxf", false);
2849 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2850 for (int i = 0; i < 24; ++i) {
2851 writer->write (j2c.data(), j2c.size());
2853 writer->finalize ();
2855 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2857 auto reel = make_shared<dcp::Reel>(
2858 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2859 make_shared<dcp::ReelSoundAsset>(ms, 0)
2862 reel->add (simple_markers());
2866 cpl->set_content_version (
2867 {"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"}
2869 cpl->set_annotation_text ("A Test DCP");
2870 cpl->set_issuer ("OpenDCP 0.0.25");
2871 cpl->set_creator ("OpenDCP 0.0.25");
2872 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2873 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2874 cpl->set_main_sound_sample_rate (48000);
2875 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2876 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2877 cpl->set_version_number (1);
2881 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2883 check_verify_result (
2886 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2891 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2893 vector<dcp::VerificationNote> notes;
2894 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"));
2895 auto reader = picture.start_read ();
2896 auto frame = reader->get_frame (0);
2897 verify_j2k (frame, notes);
2898 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2902 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2904 vector<dcp::VerificationNote> notes;
2905 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2906 auto reader = picture.start_read ();
2907 auto frame = reader->get_frame (0);
2908 verify_j2k (frame, notes);
2909 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2913 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2915 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2916 prepare_directory (dir);
2917 auto dcp = make_simple (dir);
2919 vector<dcp::VerificationNote> notes;
2920 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2921 auto reader = picture.start_read ();
2922 auto frame = reader->get_frame (0);
2923 verify_j2k (frame, notes);
2924 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2928 /** Check that ResourceID and the XML ID being different is spotted */
2929 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2931 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2932 prepare_directory (dir);
2934 ASDCP::WriterInfo writer_info;
2935 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2938 auto mxf_id = dcp::make_uuid ();
2939 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2940 BOOST_REQUIRE (c == Kumu::UUID_Length);
2942 auto resource_id = dcp::make_uuid ();
2943 ASDCP::TimedText::TimedTextDescriptor descriptor;
2944 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2945 DCP_ASSERT (c == Kumu::UUID_Length);
2947 auto xml_id = dcp::make_uuid ();
2948 ASDCP::TimedText::MXFWriter writer;
2949 auto subs_mxf = dir / "subs.mxf";
2950 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2951 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2952 writer.WriteTimedTextResource (dcp::String::compose(
2953 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2954 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2955 "<Id>urn:uuid:%1</Id>"
2956 "<ContentTitleText>Content</ContentTitleText>"
2957 "<AnnotationText>Annotation</AnnotationText>"
2958 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2959 "<ReelNumber>1</ReelNumber>"
2960 "<Language>en-US</Language>"
2961 "<EditRate>25 1</EditRate>"
2962 "<TimeCodeRate>25</TimeCodeRate>"
2963 "<StartTime>00:00:00:00</StartTime>"
2965 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2966 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2967 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2976 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2977 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2979 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2981 check_verify_result (
2984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2986 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2992 /** Check that ResourceID and the MXF ID being the same is spotted */
2993 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2995 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2996 prepare_directory (dir);
2998 ASDCP::WriterInfo writer_info;
2999 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3002 auto mxf_id = dcp::make_uuid ();
3003 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3004 BOOST_REQUIRE (c == Kumu::UUID_Length);
3006 auto resource_id = mxf_id;
3007 ASDCP::TimedText::TimedTextDescriptor descriptor;
3008 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3009 DCP_ASSERT (c == Kumu::UUID_Length);
3011 auto xml_id = resource_id;
3012 ASDCP::TimedText::MXFWriter writer;
3013 auto subs_mxf = dir / "subs.mxf";
3014 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
3015 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3016 writer.WriteTimedTextResource (dcp::String::compose(
3017 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3018 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3019 "<Id>urn:uuid:%1</Id>"
3020 "<ContentTitleText>Content</ContentTitleText>"
3021 "<AnnotationText>Annotation</AnnotationText>"
3022 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3023 "<ReelNumber>1</ReelNumber>"
3024 "<Language>en-US</Language>"
3025 "<EditRate>25 1</EditRate>"
3026 "<TimeCodeRate>25</TimeCodeRate>"
3027 "<StartTime>00:00:00:00</StartTime>"
3029 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3030 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3031 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3040 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3041 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3043 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3045 check_verify_result (
3048 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3049 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3050 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3051 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3056 /** Check a DCP with a 3D asset marked as 2D */
3057 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3059 check_verify_result (
3060 { private_test / "data" / "xm" },
3063 dcp::VerificationNote::Type::WARNING,
3064 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3067 dcp::VerificationNote::Type::BV21_ERROR,
3068 dcp::VerificationNote::Code::INVALID_STANDARD