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.
35 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset_writer.h"
43 #include "openjpeg_image.h"
44 #include "raw_convert.h"
46 #include "reel_interop_closed_caption_asset.h"
47 #include "reel_interop_subtitle_asset.h"
48 #include "reel_markers_asset.h"
49 #include "reel_mono_picture_asset.h"
50 #include "reel_sound_asset.h"
51 #include "reel_stereo_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "smpte_subtitle_asset.h"
55 #include "stereo_picture_asset.h"
56 #include "stream_operators.h"
60 #include "verify_j2k.h"
61 #include <boost/test/unit_test.hpp>
62 #include <boost/algorithm/string.hpp>
69 using std::make_shared;
71 using std::shared_ptr;
74 using boost::optional;
75 using namespace boost::filesystem;
78 static list<pair<string, optional<path>>> stages;
80 static string filename_to_id(boost::filesystem::path path)
82 return path.string().substr(4, path.string().length() - 8);
85 static boost::filesystem::path const dcp_test1_pkl = find_file("test/ref/DCP/dcp_test1", "pkl_").filename();
86 static string const dcp_test1_pkl_id = filename_to_id(dcp_test1_pkl);
88 static boost::filesystem::path const dcp_test1_cpl = find_file("test/ref/DCP/dcp_test1", "cpl_").filename();
89 static string const dcp_test1_cpl_id = filename_to_id(dcp_test1_cpl);
91 static string const dcp_test1_asset_map_id = "017b3de4-6dda-408d-b19b-6711354b0bc3";
93 static boost::filesystem::path const encryption_test_cpl = find_file("test/ref/DCP/encryption_test", "cpl_").filename();
94 static string const encryption_test_cpl_id = filename_to_id(encryption_test_cpl);
96 static boost::filesystem::path const encryption_test_pkl = find_file("test/ref/DCP/encryption_test", "pkl_").filename();
97 static string const encryption_test_pkl_id = filename_to_id(encryption_test_pkl);
100 stage (string s, optional<path> p)
102 stages.push_back (make_pair (s, p));
112 prepare_directory (path path)
114 using namespace boost::filesystem;
116 create_directories (path);
120 /** Copy dcp_test{reference_number} to build/test/verify_test{verify_test_suffix}
121 * to make a new sacrifical test DCP.
124 setup (int reference_number, string verify_test_suffix)
126 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
127 prepare_directory (dir);
128 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
129 copy_file (i.path(), dir / i.path().filename());
138 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
140 auto reel = make_shared<dcp::Reel>();
141 reel->add (reel_asset);
142 reel->add (simple_markers());
144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
146 auto dcp = make_shared<dcp::DCP>(dir);
148 dcp->set_annotation_text("hello");
155 /** Class that can alter a file by searching and replacing strings within it.
156 * On destruction modifies the file whose name was given to the constructor.
164 _content = dcp::file_to_string (_path);
169 auto f = fopen(_path.string().c_str(), "w");
171 fwrite (_content.c_str(), _content.length(), 1, f);
178 ChangeChecker(Editor* editor)
181 _old_content = _editor->_content;
186 BOOST_REQUIRE(_old_content != _editor->_content);
190 std::string _old_content;
193 void replace (string a, string b)
195 ChangeChecker cc(this);
196 boost::algorithm::replace_all (_content, a, b);
199 void delete_first_line_containing (string s)
201 ChangeChecker cc(this);
202 auto lines = as_lines();
205 for (auto i: lines) {
206 if (i.find(s) == string::npos || done) {
207 _content += i + "\n";
214 void delete_lines (string from, string to)
216 ChangeChecker cc(this);
217 auto lines = as_lines();
218 bool deleting = false;
220 for (auto i: lines) {
221 if (i.find(from) != string::npos) {
225 _content += i + "\n";
227 if (deleting && i.find(to) != string::npos) {
233 void insert (string after, string line)
235 ChangeChecker cc(this);
236 auto lines = as_lines();
238 bool replaced = false;
239 for (auto i: lines) {
240 _content += i + "\n";
241 if (!replaced && i.find(after) != string::npos) {
242 _content += line + "\n";
249 friend class ChangeChecker;
251 vector<string> as_lines() const
253 vector<string> lines;
254 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
259 std::string _content;
263 LIBDCP_DISABLE_WARNINGS
266 dump_notes (vector<dcp::VerificationNote> const & notes)
268 for (auto i: notes) {
269 std::cout << dcp::note_to_string(i) << "\n";
272 LIBDCP_ENABLE_WARNINGS
277 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
279 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
280 std::sort (notes.begin(), notes.end());
281 std::sort (test_notes.begin(), test_notes.end());
283 string message = "\nVerification notes from test:\n";
284 for (auto i: notes) {
285 message += " " + note_to_string(i) + "\n";
287 message += "Expected:\n";
288 for (auto i: test_notes) {
289 message += " " + note_to_string(i) + "\n";
292 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
296 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
297 * replacing from with to. Verify the resulting DCP and check that the results match the given
302 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
304 auto dir = setup (1, suffix);
307 Editor e (file(suffix));
308 e.replace (from, to);
311 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
313 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
314 auto i = notes.begin();
315 auto j = codes.begin();
316 while (i != notes.end()) {
317 BOOST_CHECK_EQUAL (i->code(), *j);
324 BOOST_AUTO_TEST_CASE (verify_no_error)
327 auto dir = setup (1, "no_error");
328 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
330 path const cpl_file = dir / dcp_test1_cpl;
331 path const pkl_file = dir / dcp_test1_pkl;
332 path const assetmap_file = dir / "ASSETMAP.xml";
334 auto st = stages.begin();
335 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
336 BOOST_REQUIRE (st->second);
337 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
339 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
340 BOOST_REQUIRE (st->second);
341 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
343 BOOST_CHECK_EQUAL (st->first, "Checking reel");
344 BOOST_REQUIRE (!st->second);
346 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
347 BOOST_REQUIRE (st->second);
348 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
350 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
351 BOOST_REQUIRE (st->second);
352 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
354 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
355 BOOST_REQUIRE (st->second);
356 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
358 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
359 BOOST_REQUIRE (st->second);
360 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
362 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
363 BOOST_REQUIRE (st->second);
364 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
366 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
367 BOOST_REQUIRE (st->second);
368 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
370 BOOST_REQUIRE (st == stages.end());
372 BOOST_CHECK_EQUAL (notes.size(), 0U);
376 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
378 using namespace boost::filesystem;
380 auto dir = setup (1, "incorrect_picture_sound_hash");
382 auto video_path = path(dir / "video.mxf");
383 auto mod = fopen(video_path.string().c_str(), "r+b");
385 fseek (mod, 4096, SEEK_SET);
387 fwrite (&x, sizeof(x), 1, mod);
390 auto audio_path = path(dir / "audio.mxf");
391 mod = fopen(audio_path.string().c_str(), "r+b");
393 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
394 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
397 dcp::ASDCPErrorSuspender sus;
398 check_verify_result (
401 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
402 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
407 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
409 using namespace boost::filesystem;
411 auto dir = setup (1, "mismatched_picture_sound_hashes");
414 Editor e (dir / dcp_test1_pkl);
415 e.replace ("<Hash>", "<Hash>x");
418 check_verify_result (
421 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
422 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
423 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
424 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
425 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
426 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
431 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
433 auto dir = setup (1, "failed_read_content_kind");
436 Editor e (dir / dcp_test1_cpl);
437 e.replace ("<ContentKind>", "<ContentKind>x");
440 check_verify_result (
443 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
444 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
453 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
461 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
467 asset_map (string suffix)
469 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
473 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
475 check_verify_result_after_replace (
476 "invalid_picture_frame_rate", &cpl,
477 "<FrameRate>24 1", "<FrameRate>99 1",
478 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
479 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
483 BOOST_AUTO_TEST_CASE (verify_missing_asset)
485 auto dir = setup (1, "missing_asset");
486 remove (dir / "video.mxf");
487 check_verify_result (
490 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
495 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
497 check_verify_result_after_replace (
498 "empty_asset_path", &asset_map,
499 "<Path>video.mxf</Path>", "<Path></Path>",
500 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
505 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
507 check_verify_result_after_replace (
508 "mismatched_standard", &cpl,
509 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
510 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
511 dcp::VerificationNote::Code::INVALID_XML,
512 dcp::VerificationNote::Code::INVALID_XML,
513 dcp::VerificationNote::Code::INVALID_XML,
514 dcp::VerificationNote::Code::INVALID_XML,
515 dcp::VerificationNote::Code::INVALID_XML,
516 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
521 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
523 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
524 check_verify_result_after_replace (
525 "invalid_xml_cpl_id", &cpl,
526 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
527 { dcp::VerificationNote::Code::INVALID_XML }
532 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
534 check_verify_result_after_replace (
535 "invalid_xml_issue_date", &cpl,
536 "<IssueDate>", "<IssueDate>x",
537 { dcp::VerificationNote::Code::INVALID_XML,
538 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
543 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
545 check_verify_result_after_replace (
546 "invalid_xml_pkl_id", &pkl,
547 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
548 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
549 { dcp::VerificationNote::Code::INVALID_XML }
554 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
556 check_verify_result_after_replace (
557 "invalid_xml_asset_map_id", &asset_map,
558 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
559 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
560 { dcp::VerificationNote::Code::INVALID_XML }
565 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
568 auto dir = setup (3, "verify_invalid_standard");
569 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
571 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
572 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
573 path const assetmap_file = dir / "ASSETMAP";
575 auto st = stages.begin();
576 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
577 BOOST_REQUIRE (st->second);
578 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
580 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
581 BOOST_REQUIRE (st->second);
582 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
584 BOOST_CHECK_EQUAL (st->first, "Checking reel");
585 BOOST_REQUIRE (!st->second);
587 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
588 BOOST_REQUIRE (st->second);
589 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
591 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
592 BOOST_REQUIRE (st->second);
593 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
595 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
596 BOOST_REQUIRE (st->second);
597 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
599 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
600 BOOST_REQUIRE (st->second);
601 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
603 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
604 BOOST_REQUIRE (st->second);
605 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
607 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
608 BOOST_REQUIRE (st->second);
609 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
611 BOOST_REQUIRE (st == stages.end());
613 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
614 auto i = notes.begin ();
615 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
616 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
618 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
619 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
622 /* DCP with a short asset */
623 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
625 auto dir = setup (8, "invalid_duration");
626 check_verify_result (
629 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
630 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
631 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
632 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
634 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
641 dcp_from_frame (dcp::ArrayData const& frame, path dir)
643 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
644 create_directories (dir);
645 auto writer = asset->start_write (dir / "pic.mxf", true);
646 for (int i = 0; i < 24; ++i) {
647 writer->write (frame.data(), frame.size());
651 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
652 return write_dcp_with_single_asset (dir, reel_asset);
656 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
658 int const too_big = 1302083 * 2;
660 /* Compress a black image */
661 auto image = black_image ();
662 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
663 BOOST_REQUIRE (frame.size() < too_big);
665 /* Place it in a bigger block with some zero padding at the end */
666 dcp::ArrayData oversized_frame(too_big);
667 memcpy (oversized_frame.data(), frame.data(), frame.size());
668 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
670 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
671 prepare_directory (dir);
672 auto cpl = dcp_from_frame (oversized_frame, dir);
674 check_verify_result (
677 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
678 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
679 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
684 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
686 int const nearly_too_big = 1302083 * 0.98;
688 /* Compress a black image */
689 auto image = black_image ();
690 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
691 BOOST_REQUIRE (frame.size() < nearly_too_big);
693 /* Place it in a bigger block with some zero padding at the end */
694 dcp::ArrayData oversized_frame(nearly_too_big);
695 memcpy (oversized_frame.data(), frame.data(), frame.size());
696 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
698 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
699 prepare_directory (dir);
700 auto cpl = dcp_from_frame (oversized_frame, dir);
702 check_verify_result (
705 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
706 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
707 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
712 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
714 /* Compress a black image */
715 auto image = black_image ();
716 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
717 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
719 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
720 prepare_directory (dir);
721 auto cpl = dcp_from_frame (frame, dir);
723 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
727 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
729 path const dir("build/test/verify_valid_interop_subtitles");
730 prepare_directory (dir);
731 copy_file ("test/data/subs1.xml", dir / "subs.xml");
732 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
733 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
734 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
736 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
740 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
742 using namespace boost::filesystem;
744 path const dir("build/test/verify_invalid_interop_subtitles");
745 prepare_directory (dir);
746 copy_file ("test/data/subs1.xml", dir / "subs.xml");
747 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
748 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
749 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
752 Editor e (dir / "subs.xml");
753 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
756 check_verify_result (
759 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
760 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
762 dcp::VerificationNote::Type::ERROR,
763 dcp::VerificationNote::Code::INVALID_XML,
764 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
772 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
774 path const dir("build/test/verify_valid_smpte_subtitles");
775 prepare_directory (dir);
776 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
777 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
778 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
779 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
781 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
785 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
787 using namespace boost::filesystem;
789 path const dir("build/test/verify_invalid_smpte_subtitles");
790 prepare_directory (dir);
791 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
792 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
793 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
794 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
795 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
797 check_verify_result (
800 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
802 dcp::VerificationNote::Type::ERROR,
803 dcp::VerificationNote::Code::INVALID_XML,
804 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
808 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
809 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
814 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
816 path const dir("build/test/verify_empty_text_node_in_subtitles");
817 prepare_directory (dir);
818 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
819 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
820 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
821 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
823 check_verify_result (
826 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
827 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
828 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
829 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
834 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
835 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
837 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
838 prepare_directory (dir);
839 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
840 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
841 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
842 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
844 check_verify_result (
847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
852 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
853 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
855 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
856 prepare_directory (dir);
857 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
858 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
859 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
860 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
862 check_verify_result (
865 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
866 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
871 BOOST_AUTO_TEST_CASE (verify_external_asset)
873 path const ov_dir("build/test/verify_external_asset");
874 prepare_directory (ov_dir);
876 auto image = black_image ();
877 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
878 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
879 dcp_from_frame (frame, ov_dir);
881 dcp::DCP ov (ov_dir);
884 path const vf_dir("build/test/verify_external_asset_vf");
885 prepare_directory (vf_dir);
887 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
888 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
890 check_verify_result (
893 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
899 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
901 path const dir("build/test/verify_valid_cpl_metadata");
902 prepare_directory (dir);
904 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
905 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
906 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
908 auto reel = make_shared<dcp::Reel>();
909 reel->add (reel_asset);
911 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
912 reel->add (simple_markers(16 * 24));
914 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
916 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
917 cpl->set_main_sound_sample_rate (48000);
918 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
919 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
920 cpl->set_version_number (1);
924 dcp.set_annotation_text("hello");
929 path find_cpl (path dir)
931 for (auto i: directory_iterator(dir)) {
932 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
937 BOOST_REQUIRE (false);
942 /* DCP with invalid CompositionMetadataAsset */
943 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
945 using namespace boost::filesystem;
947 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
948 prepare_directory (dir);
950 auto reel = make_shared<dcp::Reel>();
951 reel->add (black_picture_asset(dir));
952 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
954 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
955 cpl->set_main_sound_sample_rate (48000);
956 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
957 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
958 cpl->set_version_number (1);
960 reel->add (simple_markers());
964 dcp.set_annotation_text("hello");
968 Editor e (find_cpl(dir));
969 e.replace ("MainSound", "MainSoundX");
972 check_verify_result (
975 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
976 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
978 dcp::VerificationNote::Type::ERROR,
979 dcp::VerificationNote::Code::INVALID_XML,
980 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
981 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
982 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
983 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
984 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
985 "ExtensionMetadataList?,)'"),
986 canonical(cpl->file().get()),
989 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
994 /* DCP with invalid CompositionMetadataAsset */
995 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
997 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
998 prepare_directory (dir);
1000 auto reel = make_shared<dcp::Reel>();
1001 reel->add (black_picture_asset(dir));
1002 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1004 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1005 cpl->set_main_sound_sample_rate (48000);
1006 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1007 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1011 dcp.set_annotation_text("hello");
1015 Editor e (find_cpl(dir));
1016 e.replace ("meta:Width", "meta:WidthX");
1019 check_verify_result (
1021 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1026 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1028 path const dir("build/test/verify_invalid_language1");
1029 prepare_directory (dir);
1030 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1031 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1032 asset->_language = "wrong-andbad";
1033 asset->write (dir / "subs.mxf");
1034 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1035 reel_asset->_language = "badlang";
1036 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1038 check_verify_result (
1041 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1042 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1043 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1048 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1049 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1051 path const dir("build/test/verify_invalid_language2");
1052 prepare_directory (dir);
1053 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1054 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1055 asset->_language = "wrong-andbad";
1056 asset->write (dir / "subs.mxf");
1057 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1058 reel_asset->_language = "badlang";
1059 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1061 check_verify_result (
1064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1065 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1066 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1071 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1072 * the release territory.
1074 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1076 path const dir("build/test/verify_invalid_language3");
1077 prepare_directory (dir);
1079 auto picture = simple_picture (dir, "foo");
1080 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1081 auto reel = make_shared<dcp::Reel>();
1082 reel->add (reel_picture);
1083 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1084 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1085 reel->add (reel_sound);
1086 reel->add (simple_markers());
1088 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1090 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1091 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1092 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1093 cpl->set_main_sound_sample_rate (48000);
1094 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1095 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1096 cpl->set_version_number (1);
1097 cpl->_release_territory = "fred-jim";
1098 auto dcp = make_shared<dcp::DCP>(dir);
1100 dcp->set_annotation_text("hello");
1103 check_verify_result (
1106 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1107 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1108 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1109 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1115 vector<dcp::VerificationNote>
1116 check_picture_size (int width, int height, int frame_rate, bool three_d)
1118 using namespace boost::filesystem;
1120 path dcp_path = "build/test/verify_picture_test";
1121 prepare_directory (dcp_path);
1123 shared_ptr<dcp::PictureAsset> mp;
1125 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1127 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1129 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1131 auto image = black_image (dcp::Size(width, height));
1132 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1133 int const length = three_d ? frame_rate * 2 : frame_rate;
1134 for (int i = 0; i < length; ++i) {
1135 picture_writer->write (j2c.data(), j2c.size());
1137 picture_writer->finalize ();
1139 auto d = make_shared<dcp::DCP>(dcp_path);
1140 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1141 cpl->set_annotation_text ("A Test DCP");
1142 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1143 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1144 cpl->set_main_sound_sample_rate (48000);
1145 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1146 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1147 cpl->set_version_number (1);
1149 auto reel = make_shared<dcp::Reel>();
1152 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1154 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1157 reel->add (simple_markers(frame_rate));
1162 d->set_annotation_text("A Test DCP");
1165 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1171 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1173 auto notes = check_picture_size(width, height, frame_rate, three_d);
1174 BOOST_CHECK_EQUAL (notes.size(), 0U);
1180 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1182 auto notes = check_picture_size(width, height, frame_rate, three_d);
1183 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1184 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1185 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1191 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1193 auto notes = check_picture_size(width, height, frame_rate, three_d);
1194 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1195 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1196 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1202 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1204 auto notes = check_picture_size(width, height, frame_rate, three_d);
1205 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1206 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1207 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1211 BOOST_AUTO_TEST_CASE (verify_picture_size)
1213 using namespace boost::filesystem;
1216 check_picture_size_ok (2048, 858, 24, false);
1217 check_picture_size_ok (2048, 858, 25, false);
1218 check_picture_size_ok (2048, 858, 48, false);
1219 check_picture_size_ok (2048, 858, 24, true);
1220 check_picture_size_ok (2048, 858, 25, true);
1221 check_picture_size_ok (2048, 858, 48, true);
1224 check_picture_size_ok (1998, 1080, 24, false);
1225 check_picture_size_ok (1998, 1080, 25, false);
1226 check_picture_size_ok (1998, 1080, 48, false);
1227 check_picture_size_ok (1998, 1080, 24, true);
1228 check_picture_size_ok (1998, 1080, 25, true);
1229 check_picture_size_ok (1998, 1080, 48, true);
1232 check_picture_size_ok (4096, 1716, 24, false);
1235 check_picture_size_ok (3996, 2160, 24, false);
1237 /* Bad frame size */
1238 check_picture_size_bad_frame_size (2050, 858, 24, false);
1239 check_picture_size_bad_frame_size (2048, 658, 25, false);
1240 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1241 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1243 /* Bad 2K frame rate */
1244 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1245 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1246 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1248 /* Bad 4K frame rate */
1249 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1250 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1253 auto notes = check_picture_size(3996, 2160, 24, true);
1254 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1255 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1256 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1262 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, dcp::VAlign v_align = dcp::VAlign::CENTER, string text = "Hello")
1265 std::make_shared<dcp::SubtitleString>(
1273 dcp::Time(start_frame, 24, 24),
1274 dcp::Time(end_frame, 24, 24),
1276 dcp::HAlign::CENTER,
1280 dcp::Direction::LTR,
1292 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1294 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1295 prepare_directory (dir);
1297 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1298 for (int i = 0; i < 2048; ++i) {
1299 add_test_subtitle (asset, i * 24, i * 24 + 20);
1301 asset->set_language (dcp::LanguageTag("de-DE"));
1302 asset->write (dir / "subs.mxf");
1303 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1304 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1306 check_verify_result (
1309 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1311 dcp::VerificationNote::Type::BV21_ERROR,
1312 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1314 canonical(dir / "subs.mxf")
1316 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1317 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1323 shared_ptr<dcp::SMPTESubtitleAsset>
1324 make_large_subtitle_asset (path font_file)
1326 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1327 dcp::ArrayData big_fake_font(1024 * 1024);
1328 big_fake_font.write (font_file);
1329 for (int i = 0; i < 116; ++i) {
1330 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1338 verify_timed_text_asset_too_large (string name)
1340 auto const dir = path("build/test") / name;
1341 prepare_directory (dir);
1342 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1343 add_test_subtitle (asset, 0, 240);
1344 asset->set_language (dcp::LanguageTag("de-DE"));
1345 asset->write (dir / "subs.mxf");
1347 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1348 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1350 check_verify_result (
1353 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1354 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1355 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1356 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1357 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1362 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1364 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1365 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1369 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1371 path dir = "build/test/verify_missing_subtitle_language";
1372 prepare_directory (dir);
1373 auto dcp = make_simple (dir, 1, 106);
1376 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1377 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1378 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1379 "<ContentTitleText>Content</ContentTitleText>"
1380 "<AnnotationText>Annotation</AnnotationText>"
1381 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1382 "<ReelNumber>1</ReelNumber>"
1383 "<EditRate>24 1</EditRate>"
1384 "<TimeCodeRate>24</TimeCodeRate>"
1385 "<StartTime>00:00:00:00</StartTime>"
1386 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1388 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1389 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1390 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1396 dcp::File xml_file(dir / "subs.xml", "w");
1397 BOOST_REQUIRE (xml_file);
1398 xml_file.write(xml.c_str(), xml.size(), 1);
1400 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1401 subs->write (dir / "subs.mxf");
1403 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1404 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1405 dcp->set_annotation_text("A Test DCP");
1408 check_verify_result (
1411 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1412 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1417 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1419 path path ("build/test/verify_mismatched_subtitle_languages");
1420 auto constexpr reel_length = 192;
1421 auto dcp = make_simple (path, 2, reel_length);
1422 auto cpl = dcp->cpls()[0];
1425 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1426 subs->set_language (dcp::LanguageTag("de-DE"));
1427 subs->add (simple_subtitle());
1428 subs->write (path / "subs1.mxf");
1429 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1430 cpl->reels()[0]->add(reel_subs);
1434 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1435 subs->set_language (dcp::LanguageTag("en-US"));
1436 subs->add (simple_subtitle());
1437 subs->write (path / "subs2.mxf");
1438 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1439 cpl->reels()[1]->add(reel_subs);
1442 dcp->set_annotation_text("A Test DCP");
1445 check_verify_result (
1448 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1449 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1450 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1455 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1457 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1458 auto constexpr reel_length = 192;
1459 auto dcp = make_simple (path, 2, reel_length);
1460 auto cpl = dcp->cpls()[0];
1463 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1464 ccaps->set_language (dcp::LanguageTag("de-DE"));
1465 ccaps->add (simple_subtitle());
1466 ccaps->write (path / "subs1.mxf");
1467 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1468 cpl->reels()[0]->add(reel_ccaps);
1472 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1473 ccaps->set_language (dcp::LanguageTag("en-US"));
1474 ccaps->add (simple_subtitle());
1475 ccaps->write (path / "subs2.mxf");
1476 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1477 cpl->reels()[1]->add(reel_ccaps);
1480 dcp->set_annotation_text("A Test DCP");
1483 check_verify_result (
1486 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1487 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1492 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1494 path dir = "build/test/verify_missing_subtitle_start_time";
1495 prepare_directory (dir);
1496 auto dcp = make_simple (dir, 1, 106);
1499 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1500 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1501 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1502 "<ContentTitleText>Content</ContentTitleText>"
1503 "<AnnotationText>Annotation</AnnotationText>"
1504 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1505 "<ReelNumber>1</ReelNumber>"
1506 "<Language>de-DE</Language>"
1507 "<EditRate>24 1</EditRate>"
1508 "<TimeCodeRate>24</TimeCodeRate>"
1509 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1511 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1512 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1513 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1519 dcp::File xml_file(dir / "subs.xml", "w");
1520 BOOST_REQUIRE (xml_file);
1521 xml_file.write(xml.c_str(), xml.size(), 1);
1523 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1524 subs->write (dir / "subs.mxf");
1526 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1527 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1528 dcp->set_annotation_text("A Test DCP");
1531 check_verify_result (
1534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1535 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1540 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1542 path dir = "build/test/verify_invalid_subtitle_start_time";
1543 prepare_directory (dir);
1544 auto dcp = make_simple (dir, 1, 106);
1547 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1548 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1549 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1550 "<ContentTitleText>Content</ContentTitleText>"
1551 "<AnnotationText>Annotation</AnnotationText>"
1552 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1553 "<ReelNumber>1</ReelNumber>"
1554 "<Language>de-DE</Language>"
1555 "<EditRate>24 1</EditRate>"
1556 "<TimeCodeRate>24</TimeCodeRate>"
1557 "<StartTime>00:00:02:00</StartTime>"
1558 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1560 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1561 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1562 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1568 dcp::File xml_file(dir / "subs.xml", "w");
1569 BOOST_REQUIRE (xml_file);
1570 xml_file.write(xml.c_str(), xml.size(), 1);
1572 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1573 subs->write (dir / "subs.mxf");
1575 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1576 dcp->cpls().front()->reels().front()->add(reel_subs);
1577 dcp->set_annotation_text("A Test DCP");
1580 check_verify_result (
1583 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1584 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1592 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1595 , v_position(v_position_)
1603 dcp::VAlign v_align;
1609 shared_ptr<dcp::CPL>
1610 dcp_with_text (path dir, vector<TestText> subs)
1612 prepare_directory (dir);
1613 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1614 asset->set_start_time (dcp::Time());
1615 for (auto i: subs) {
1616 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1618 asset->set_language (dcp::LanguageTag("de-DE"));
1619 asset->write (dir / "subs.mxf");
1621 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1622 return write_dcp_with_single_asset (dir, reel_asset);
1627 shared_ptr<dcp::CPL>
1628 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1630 prepare_directory (dir);
1631 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1632 asset->set_start_time (dcp::Time());
1633 asset->set_language (dcp::LanguageTag("de-DE"));
1635 auto subs_mxf = dir / "subs.mxf";
1636 asset->write (subs_mxf);
1638 /* The call to write() puts the asset into the DCP correctly but it will have
1639 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1642 ASDCP::TimedText::MXFWriter writer;
1643 ASDCP::WriterInfo writer_info;
1644 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1646 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1647 DCP_ASSERT (c == Kumu::UUID_Length);
1648 ASDCP::TimedText::TimedTextDescriptor descriptor;
1649 descriptor.ContainerDuration = asset->intrinsic_duration();
1650 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1651 DCP_ASSERT (c == Kumu::UUID_Length);
1652 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1653 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1654 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1655 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1658 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1659 return write_dcp_with_single_asset (dir, reel_asset);
1663 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1665 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1666 /* Just too early */
1667 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1668 check_verify_result (
1671 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1678 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1680 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1681 /* Just late enough */
1682 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1683 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1687 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1689 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1690 prepare_directory (dir);
1692 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1693 asset1->set_start_time (dcp::Time());
1694 /* Just late enough */
1695 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1696 asset1->set_language (dcp::LanguageTag("de-DE"));
1697 asset1->write (dir / "subs1.mxf");
1698 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1699 auto reel1 = make_shared<dcp::Reel>();
1700 reel1->add (reel_asset1);
1701 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1702 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1703 reel1->add (markers1);
1705 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1706 asset2->set_start_time (dcp::Time());
1707 /* This would be too early on first reel but should be OK on the second */
1708 add_test_subtitle (asset2, 3, 4 * 24);
1709 asset2->set_language (dcp::LanguageTag("de-DE"));
1710 asset2->write (dir / "subs2.mxf");
1711 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1712 auto reel2 = make_shared<dcp::Reel>();
1713 reel2->add (reel_asset2);
1714 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1715 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1716 reel2->add (markers2);
1718 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1721 auto dcp = make_shared<dcp::DCP>(dir);
1723 dcp->set_annotation_text("hello");
1726 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1730 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1732 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1733 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1737 { 5 * 24 + 1, 6 * 24 },
1739 check_verify_result (
1742 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1743 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1748 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1750 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1751 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1755 { 5 * 24 + 16, 8 * 24 },
1757 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1761 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1763 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1764 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1765 check_verify_result (
1768 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1774 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1776 auto const dir = path("build/test/verify_valid_subtitle_duration");
1777 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1778 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1782 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1784 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1785 prepare_directory (dir);
1786 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1787 asset->set_start_time (dcp::Time());
1788 add_test_subtitle (asset, 0, 4 * 24);
1789 asset->set_language (dcp::LanguageTag("de-DE"));
1790 asset->write (dir / "subs.mxf");
1792 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1793 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1794 check_verify_result (
1797 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1798 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1800 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1806 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1808 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1809 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1812 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1813 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1814 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1815 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1817 check_verify_result (
1820 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1826 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1828 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1829 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1832 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1833 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1834 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1836 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1840 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1842 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1843 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1846 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1847 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1848 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1849 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1851 check_verify_result (
1854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1855 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1860 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1862 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1863 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1866 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1867 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1868 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1869 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1871 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1875 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1877 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1878 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1881 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1883 check_verify_result (
1886 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1892 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1894 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1895 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1898 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1900 check_verify_result (
1903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1904 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1909 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1911 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1912 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1915 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1916 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1917 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1918 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1920 check_verify_result (
1923 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1929 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1931 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1932 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1935 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1936 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1937 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1939 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1943 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1945 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1946 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1949 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1950 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1951 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1952 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1954 check_verify_result (
1957 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1958 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1963 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1965 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1966 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1969 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1970 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1971 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1972 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1974 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1978 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
1980 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
1981 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1984 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
1986 check_verify_result (
1989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1994 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1996 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1997 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2000 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2002 check_verify_result (
2005 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2006 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2011 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2013 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2014 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2017 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2018 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2019 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2021 check_verify_result (
2024 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2029 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2031 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2032 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2035 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2036 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2037 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2039 check_verify_result (
2042 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2043 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2048 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2050 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2051 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2054 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2055 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2056 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2058 check_verify_result (
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2066 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2068 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2069 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2072 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2073 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2074 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2076 check_verify_result (
2079 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2084 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2086 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2087 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2088 check_verify_result (
2091 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2092 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2097 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2099 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2100 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2101 check_verify_result (
2104 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2110 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2112 path const dir("build/test/verify_invalid_sound_frame_rate");
2113 prepare_directory (dir);
2115 auto picture = simple_picture (dir, "foo");
2116 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2117 auto reel = make_shared<dcp::Reel>();
2118 reel->add (reel_picture);
2119 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2120 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2121 reel->add (reel_sound);
2122 reel->add (simple_markers());
2123 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2125 auto dcp = make_shared<dcp::DCP>(dir);
2127 dcp->set_annotation_text("hello");
2130 check_verify_result (
2133 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2134 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2139 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2141 path const dir("build/test/verify_missing_cpl_annotation_text");
2142 auto dcp = make_simple (dir);
2143 dcp->set_annotation_text("A Test DCP");
2146 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2148 auto const cpl = dcp->cpls()[0];
2151 BOOST_REQUIRE (cpl->file());
2152 Editor e(cpl->file().get());
2153 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2156 check_verify_result (
2159 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2160 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2165 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2167 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2168 auto dcp = make_simple (dir);
2169 dcp->set_annotation_text("A Test DCP");
2172 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2173 auto const cpl = dcp->cpls()[0];
2176 BOOST_REQUIRE (cpl->file());
2177 Editor e(cpl->file().get());
2178 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2181 check_verify_result (
2184 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2185 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2190 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2192 path const dir("build/test/verify_mismatched_asset_duration");
2193 prepare_directory (dir);
2194 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2195 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2197 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2198 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2200 auto reel = make_shared<dcp::Reel>(
2201 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2202 make_shared<dcp::ReelSoundAsset>(ms, 0)
2205 reel->add (simple_markers());
2209 dcp->set_annotation_text("A Test DCP");
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2216 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2223 shared_ptr<dcp::CPL>
2224 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2226 prepare_directory (dir);
2227 auto dcp = make_shared<dcp::DCP>(dir);
2228 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2230 auto constexpr reel_length = 192;
2232 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2233 subs->set_language (dcp::LanguageTag("de-DE"));
2234 subs->set_start_time (dcp::Time());
2235 subs->add (simple_subtitle());
2236 subs->write (dir / "subs.mxf");
2237 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2239 auto reel1 = make_shared<dcp::Reel>(
2240 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2241 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2245 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2248 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2249 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2250 reel1->add (markers1);
2254 auto reel2 = make_shared<dcp::Reel>(
2255 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2256 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2260 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2263 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2264 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2265 reel2->add (markers2);
2270 dcp->set_annotation_text("A Test DCP");
2277 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2280 path dir ("build/test/missing_main_subtitle_from_some_reels");
2281 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2282 check_verify_result (
2285 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2286 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2292 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2293 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2294 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2298 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2299 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2300 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2306 shared_ptr<dcp::CPL>
2307 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2309 prepare_directory (dir);
2310 auto dcp = make_shared<dcp::DCP>(dir);
2311 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2313 auto constexpr reel_length = 192;
2315 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2316 subs->set_language (dcp::LanguageTag("de-DE"));
2317 subs->set_start_time (dcp::Time());
2318 subs->add (simple_subtitle());
2319 subs->write (dir / "subs.mxf");
2321 auto reel1 = make_shared<dcp::Reel>(
2322 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2323 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2326 for (int i = 0; i < caps_in_reel1; ++i) {
2327 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2330 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2331 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2332 reel1->add (markers1);
2336 auto reel2 = make_shared<dcp::Reel>(
2337 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2338 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2341 for (int i = 0; i < caps_in_reel2; ++i) {
2342 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2345 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2346 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2347 reel2->add (markers2);
2352 dcp->set_annotation_text("A Test DCP");
2359 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2362 path dir ("build/test/mismatched_closed_caption_asset_counts");
2363 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2364 check_verify_result (
2367 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2368 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2373 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2374 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2375 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2379 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2380 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2381 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2388 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2390 prepare_directory (dir);
2391 auto dcp = make_shared<dcp::DCP>(dir);
2392 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2394 auto constexpr reel_length = 192;
2396 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2397 subs->set_language (dcp::LanguageTag("de-DE"));
2398 subs->set_start_time (dcp::Time());
2399 subs->add (simple_subtitle());
2400 subs->write (dir / "subs.mxf");
2401 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2404 auto reel = make_shared<dcp::Reel>(
2405 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2406 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2409 reel->add (reel_text);
2411 reel->add (simple_markers(reel_length));
2416 dcp->set_annotation_text("A Test DCP");
2419 check_verify_result (
2422 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2423 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2428 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2430 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2431 "build/test/verify_subtitle_entry_point_must_be_present",
2432 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2433 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2434 asset->unset_entry_point ();
2438 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2439 "build/test/verify_subtitle_entry_point_must_be_zero",
2440 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2441 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2442 asset->set_entry_point (4);
2446 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2447 "build/test/verify_closed_caption_entry_point_must_be_present",
2448 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2449 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2450 asset->unset_entry_point ();
2454 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2455 "build/test/verify_closed_caption_entry_point_must_be_zero",
2456 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2457 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2458 asset->set_entry_point (9);
2464 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2468 path const dir("build/test/verify_missing_hash");
2469 auto dcp = make_simple (dir);
2470 dcp->set_annotation_text("A Test DCP");
2473 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2474 auto const cpl = dcp->cpls()[0];
2475 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2476 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2477 auto asset_id = cpl->reels()[0]->main_picture()->id();
2480 BOOST_REQUIRE (cpl->file());
2481 Editor e(cpl->file().get());
2482 e.delete_first_line_containing("<Hash>");
2485 check_verify_result (
2488 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2489 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2496 verify_markers_test (
2498 vector<pair<dcp::Marker, dcp::Time>> markers,
2499 vector<dcp::VerificationNote> test_notes
2502 auto dcp = make_simple (dir);
2503 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2504 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2505 for (auto const& i: markers) {
2506 markers_asset->set (i.first, i.second);
2508 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2509 dcp->set_annotation_text("A Test DCP");
2512 check_verify_result ({dir}, test_notes);
2516 BOOST_AUTO_TEST_CASE (verify_markers)
2518 verify_markers_test (
2519 "build/test/verify_markers_all_correct",
2521 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2522 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2523 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2524 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2529 verify_markers_test (
2530 "build/test/verify_markers_missing_ffec",
2532 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2533 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2534 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2537 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2540 verify_markers_test (
2541 "build/test/verify_markers_missing_ffmc",
2543 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2544 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2545 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2548 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2551 verify_markers_test (
2552 "build/test/verify_markers_missing_ffoc",
2554 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2555 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2556 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2559 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2562 verify_markers_test (
2563 "build/test/verify_markers_missing_lfoc",
2565 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2566 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2567 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2570 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2573 verify_markers_test (
2574 "build/test/verify_markers_incorrect_ffoc",
2576 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2577 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2578 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2579 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2582 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2585 verify_markers_test (
2586 "build/test/verify_markers_incorrect_lfoc",
2588 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2589 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2590 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2591 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2594 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2599 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2601 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2602 prepare_directory (dir);
2603 auto dcp = make_simple (dir);
2604 auto cpl = dcp->cpls()[0];
2605 cpl->unset_version_number();
2606 dcp->set_annotation_text("A Test DCP");
2609 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2613 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2615 path dir = "build/test/verify_missing_extension_metadata1";
2616 auto dcp = make_simple (dir);
2617 dcp->set_annotation_text("A Test DCP");
2620 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2621 auto cpl = dcp->cpls()[0];
2624 Editor e (cpl->file().get());
2625 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2628 check_verify_result (
2631 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2632 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2637 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2639 path dir = "build/test/verify_missing_extension_metadata2";
2640 auto dcp = make_simple (dir);
2641 dcp->set_annotation_text("A Test DCP");
2644 auto cpl = dcp->cpls()[0];
2647 Editor e (cpl->file().get());
2648 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2651 check_verify_result (
2654 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2660 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2662 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2663 auto dcp = make_simple (dir);
2664 dcp->set_annotation_text("A Test DCP");
2667 auto const cpl = dcp->cpls()[0];
2670 Editor e (cpl->file().get());
2671 e.replace ("<meta:Name>A", "<meta:NameX>A");
2672 e.replace ("n</meta:Name>", "n</meta:NameX>");
2675 check_verify_result (
2678 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2679 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 },
2680 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2685 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2687 path dir = "build/test/verify_invalid_extension_metadata1";
2688 auto dcp = make_simple (dir);
2689 dcp->set_annotation_text("A Test DCP");
2692 auto cpl = dcp->cpls()[0];
2695 Editor e (cpl->file().get());
2696 e.replace ("Application", "Fred");
2699 check_verify_result (
2702 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2708 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2710 path dir = "build/test/verify_invalid_extension_metadata2";
2711 auto dcp = make_simple (dir);
2712 dcp->set_annotation_text("A Test DCP");
2715 auto cpl = dcp->cpls()[0];
2718 Editor e (cpl->file().get());
2719 e.replace ("DCP Constraints Profile", "Fred");
2722 check_verify_result (
2725 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2731 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2733 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2734 auto dcp = make_simple (dir);
2735 dcp->set_annotation_text("A Test DCP");
2738 auto const cpl = dcp->cpls()[0];
2741 Editor e (cpl->file().get());
2742 e.replace ("<meta:Value>", "<meta:ValueX>");
2743 e.replace ("</meta:Value>", "</meta:ValueX>");
2746 check_verify_result (
2749 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2750 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 75 },
2751 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2756 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2758 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2759 auto dcp = make_simple (dir);
2760 dcp->set_annotation_text("A Test DCP");
2763 auto const cpl = dcp->cpls()[0];
2766 Editor e (cpl->file().get());
2767 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2770 check_verify_result (
2773 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2774 { 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() },
2779 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2781 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2782 auto dcp = make_simple (dir);
2783 dcp->set_annotation_text("A Test DCP");
2786 auto const cpl = dcp->cpls()[0];
2789 Editor e (cpl->file().get());
2790 e.replace ("<meta:Property>", "<meta:PropertyX>");
2791 e.replace ("</meta:Property>", "</meta:PropertyX>");
2794 check_verify_result (
2797 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2798 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2799 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2804 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2806 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2807 auto dcp = make_simple (dir);
2808 dcp->set_annotation_text("A Test DCP");
2811 auto const cpl = dcp->cpls()[0];
2814 Editor e (cpl->file().get());
2815 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2816 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2819 check_verify_result (
2822 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2823 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 77 },
2824 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2830 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2832 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2833 prepare_directory (dir);
2834 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2835 copy_file (i.path(), dir / i.path().filename());
2838 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2839 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2843 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2846 check_verify_result (
2849 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2851 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2852 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2853 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2854 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2855 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2861 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2863 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2864 prepare_directory (dir);
2865 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2866 copy_file (i.path(), dir / i.path().filename());
2869 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2870 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2873 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2876 check_verify_result (
2879 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2880 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2881 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2882 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2883 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2890 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2892 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2893 prepare_directory (dir);
2894 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2895 copy_file (i.path(), dir / i.path().filename());
2899 Editor e (dir / dcp_test1_pkl);
2900 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2903 check_verify_result ({dir}, {});
2907 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2909 path dir ("build/test/verify_must_not_be_partially_encrypted");
2910 prepare_directory (dir);
2914 auto signer = make_shared<dcp::CertificateChain>();
2915 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2916 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2917 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2918 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2920 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2924 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2927 auto writer = mp->start_write (dir / "video.mxf", false);
2928 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2929 for (int i = 0; i < 24; ++i) {
2930 writer->write (j2c.data(), j2c.size());
2932 writer->finalize ();
2934 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2936 auto reel = make_shared<dcp::Reel>(
2937 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2938 make_shared<dcp::ReelSoundAsset>(ms, 0)
2941 reel->add (simple_markers());
2945 cpl->set_content_version (
2946 {"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"}
2948 cpl->set_annotation_text ("A Test DCP");
2949 cpl->set_issuer ("OpenDCP 0.0.25");
2950 cpl->set_creator ("OpenDCP 0.0.25");
2951 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2952 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2953 cpl->set_main_sound_sample_rate (48000);
2954 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2955 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2956 cpl->set_version_number (1);
2960 d.set_issuer("OpenDCP 0.0.25");
2961 d.set_creator("OpenDCP 0.0.25");
2962 d.set_issue_date("2012-07-17T04:45:18+00:00");
2963 d.set_annotation_text("A Test DCP");
2964 d.write_xml(signer);
2966 check_verify_result (
2969 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2974 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2976 vector<dcp::VerificationNote> notes;
2977 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"));
2978 auto reader = picture.start_read ();
2979 auto frame = reader->get_frame (0);
2980 verify_j2k (frame, notes);
2981 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2985 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2987 vector<dcp::VerificationNote> notes;
2988 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2989 auto reader = picture.start_read ();
2990 auto frame = reader->get_frame (0);
2991 verify_j2k (frame, notes);
2992 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2996 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2998 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2999 prepare_directory (dir);
3000 auto dcp = make_simple (dir);
3002 vector<dcp::VerificationNote> notes;
3003 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3004 auto reader = picture.start_read ();
3005 auto frame = reader->get_frame (0);
3006 verify_j2k (frame, notes);
3007 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3011 /** Check that ResourceID and the XML ID being different is spotted */
3012 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3014 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3015 prepare_directory (dir);
3017 ASDCP::WriterInfo writer_info;
3018 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3021 auto mxf_id = dcp::make_uuid ();
3022 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3023 BOOST_REQUIRE (c == Kumu::UUID_Length);
3025 auto resource_id = dcp::make_uuid ();
3026 ASDCP::TimedText::TimedTextDescriptor descriptor;
3027 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3028 DCP_ASSERT (c == Kumu::UUID_Length);
3030 auto xml_id = dcp::make_uuid ();
3031 ASDCP::TimedText::MXFWriter writer;
3032 auto subs_mxf = dir / "subs.mxf";
3033 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3034 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3035 writer.WriteTimedTextResource (dcp::String::compose(
3036 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3037 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3038 "<Id>urn:uuid:%1</Id>"
3039 "<ContentTitleText>Content</ContentTitleText>"
3040 "<AnnotationText>Annotation</AnnotationText>"
3041 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3042 "<ReelNumber>1</ReelNumber>"
3043 "<Language>en-US</Language>"
3044 "<EditRate>25 1</EditRate>"
3045 "<TimeCodeRate>25</TimeCodeRate>"
3046 "<StartTime>00:00:00:00</StartTime>"
3048 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3049 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3050 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3059 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3060 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3062 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3064 check_verify_result (
3067 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3068 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3069 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3070 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3075 /** Check that ResourceID and the MXF ID being the same is spotted */
3076 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3078 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3079 prepare_directory (dir);
3081 ASDCP::WriterInfo writer_info;
3082 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3085 auto mxf_id = dcp::make_uuid ();
3086 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3087 BOOST_REQUIRE (c == Kumu::UUID_Length);
3089 auto resource_id = mxf_id;
3090 ASDCP::TimedText::TimedTextDescriptor descriptor;
3091 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3092 DCP_ASSERT (c == Kumu::UUID_Length);
3094 auto xml_id = resource_id;
3095 ASDCP::TimedText::MXFWriter writer;
3096 auto subs_mxf = dir / "subs.mxf";
3097 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3098 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3099 writer.WriteTimedTextResource (dcp::String::compose(
3100 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3101 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3102 "<Id>urn:uuid:%1</Id>"
3103 "<ContentTitleText>Content</ContentTitleText>"
3104 "<AnnotationText>Annotation</AnnotationText>"
3105 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3106 "<ReelNumber>1</ReelNumber>"
3107 "<Language>en-US</Language>"
3108 "<EditRate>25 1</EditRate>"
3109 "<TimeCodeRate>25</TimeCodeRate>"
3110 "<StartTime>00:00:00:00</StartTime>"
3112 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3113 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3114 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3123 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3124 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3126 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3128 check_verify_result (
3131 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3132 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3133 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3134 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3139 /** Check a DCP with a 3D asset marked as 2D */
3140 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3142 check_verify_result (
3143 { private_test / "data" / "xm" },
3146 dcp::VerificationNote::Type::WARNING,
3147 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3150 dcp::VerificationNote::Type::BV21_ERROR,
3151 dcp::VerificationNote::Code::INVALID_STANDARD
3158 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3160 path dir = "build/test/verify_unexpected_things_in_main_markers";
3161 prepare_directory (dir);
3162 auto dcp = make_simple (dir, 1, 24);
3163 dcp->set_annotation_text("A Test DCP");
3167 Editor e (find_cpl(dir));
3169 " <IntrinsicDuration>24</IntrinsicDuration>",
3170 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3174 dcp::CPL cpl (find_cpl(dir));
3176 check_verify_result (
3179 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3180 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3181 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3186 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3188 path dir = "build/test/verify_invalid_content_kind";
3189 prepare_directory (dir);
3190 auto dcp = make_simple (dir, 1, 24);
3191 dcp->set_annotation_text("A Test DCP");
3195 Editor e(find_cpl(dir));
3196 e.replace("trailer", "trip");
3199 dcp::CPL cpl (find_cpl(dir));
3201 check_verify_result (
3204 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3205 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3211 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3213 path dir = "build/test/verify_valid_content_kind";
3214 prepare_directory (dir);
3215 auto dcp = make_simple (dir, 1, 24);
3216 dcp->set_annotation_text("A Test DCP");
3220 Editor e(find_cpl(dir));
3221 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3224 dcp::CPL cpl (find_cpl(dir));
3226 check_verify_result (
3229 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },