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";
248 void delete_lines_after(string after, int lines_to_delete)
250 ChangeChecker cc(this);
251 auto lines = as_lines();
253 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
254 return line.find(after) != string::npos;
257 for (auto i = lines.begin(); i != lines.end(); ++i) {
259 to_delete = lines_to_delete;
260 _content += *i + "\n";
261 } else if (to_delete == 0) {
262 _content += *i + "\n";
270 friend class ChangeChecker;
272 vector<string> as_lines() const
274 vector<string> lines;
275 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
280 std::string _content;
284 LIBDCP_DISABLE_WARNINGS
287 dump_notes (vector<dcp::VerificationNote> const & notes)
289 for (auto i: notes) {
290 std::cout << dcp::note_to_string(i) << "\n";
293 LIBDCP_ENABLE_WARNINGS
298 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
300 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
301 std::sort (notes.begin(), notes.end());
302 std::sort (test_notes.begin(), test_notes.end());
304 string message = "\nVerification notes from test:\n";
305 for (auto i: notes) {
306 message += " " + note_to_string(i) + "\n";
308 message += "Expected:\n";
309 for (auto i: test_notes) {
310 message += " " + note_to_string(i) + "\n";
313 BOOST_REQUIRE_MESSAGE (notes == test_notes, message);
317 /* Copy dcp_test1 to build/test/verify_test{suffix} then edit a file found by the functor 'file',
318 * replacing from with to. Verify the resulting DCP and check that the results match the given
323 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
325 auto dir = setup (1, suffix);
328 Editor e (file(suffix));
329 e.replace (from, to);
332 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
334 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
335 auto i = notes.begin();
336 auto j = codes.begin();
337 while (i != notes.end()) {
338 BOOST_CHECK_EQUAL (i->code(), *j);
345 BOOST_AUTO_TEST_CASE (verify_no_error)
348 auto dir = setup (1, "no_error");
349 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
351 path const cpl_file = dir / dcp_test1_cpl;
352 path const pkl_file = dir / dcp_test1_pkl;
353 path const assetmap_file = dir / "ASSETMAP.xml";
355 auto st = stages.begin();
356 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
357 BOOST_REQUIRE (st->second);
358 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
360 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
361 BOOST_REQUIRE (st->second);
362 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
364 BOOST_CHECK_EQUAL (st->first, "Checking reel");
365 BOOST_REQUIRE (!st->second);
367 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
368 BOOST_REQUIRE (st->second);
369 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
371 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
372 BOOST_REQUIRE (st->second);
373 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
375 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
376 BOOST_REQUIRE (st->second);
377 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
379 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
380 BOOST_REQUIRE (st->second);
381 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
383 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
384 BOOST_REQUIRE (st->second);
385 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
387 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
388 BOOST_REQUIRE (st->second);
389 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
391 BOOST_REQUIRE (st == stages.end());
393 BOOST_CHECK_EQUAL (notes.size(), 0U);
397 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
399 using namespace boost::filesystem;
401 auto dir = setup (1, "incorrect_picture_sound_hash");
403 auto video_path = path(dir / "video.mxf");
404 auto mod = fopen(video_path.string().c_str(), "r+b");
406 fseek (mod, 4096, SEEK_SET);
408 fwrite (&x, sizeof(x), 1, mod);
411 auto audio_path = path(dir / "audio.mxf");
412 mod = fopen(audio_path.string().c_str(), "r+b");
414 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
415 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
418 dcp::ASDCPErrorSuspender sus;
419 check_verify_result (
422 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
423 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
428 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
430 using namespace boost::filesystem;
432 auto dir = setup (1, "mismatched_picture_sound_hashes");
435 Editor e (dir / dcp_test1_pkl);
436 e.replace ("<Hash>", "<Hash>x");
439 check_verify_result (
442 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
443 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
444 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
445 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xKcJb7S2K5cNm8RG4kfQD5FTeS0A=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 },
446 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xtfX1mVIKJCVr1m7Y32Nzxf0+Rpw=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
447 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xwUmt8G+cFFKMGt0ueS9+F1S4uhc=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
452 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
454 auto dir = setup (1, "failed_read_content_kind");
457 Editor e (dir / dcp_test1_cpl);
458 e.replace ("<ContentKind>", "<ContentKind>x");
461 check_verify_result (
464 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
465 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("xtrailer") }
474 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
482 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
488 asset_map (string suffix)
490 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
494 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
496 check_verify_result_after_replace (
497 "invalid_picture_frame_rate", &cpl,
498 "<FrameRate>24 1", "<FrameRate>99 1",
499 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
500 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
504 BOOST_AUTO_TEST_CASE (verify_missing_asset)
506 auto dir = setup (1, "missing_asset");
507 remove (dir / "video.mxf");
508 check_verify_result (
511 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
516 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
518 check_verify_result_after_replace (
519 "empty_asset_path", &asset_map,
520 "<Path>video.mxf</Path>", "<Path></Path>",
521 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
526 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
528 check_verify_result_after_replace (
529 "mismatched_standard", &cpl,
530 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
531 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
532 dcp::VerificationNote::Code::INVALID_XML,
533 dcp::VerificationNote::Code::INVALID_XML,
534 dcp::VerificationNote::Code::INVALID_XML,
535 dcp::VerificationNote::Code::INVALID_XML,
536 dcp::VerificationNote::Code::INVALID_XML,
537 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
542 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
544 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
545 check_verify_result_after_replace (
546 "invalid_xml_cpl_id", &cpl,
547 "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358ab", "<Id>urn:uuid:6affb8ee-0020-4dff-a53c-17652f6358a",
548 { dcp::VerificationNote::Code::INVALID_XML }
553 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
555 check_verify_result_after_replace (
556 "invalid_xml_issue_date", &cpl,
557 "<IssueDate>", "<IssueDate>x",
558 { dcp::VerificationNote::Code::INVALID_XML,
559 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
564 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
566 check_verify_result_after_replace (
567 "invalid_xml_pkl_id", &pkl,
568 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
569 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
570 { dcp::VerificationNote::Code::INVALID_XML }
575 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
577 check_verify_result_after_replace (
578 "invalid_xml_asset_map_id", &asset_map,
579 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
580 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
581 { dcp::VerificationNote::Code::INVALID_XML }
586 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
589 auto dir = setup (3, "verify_invalid_standard");
590 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
592 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
593 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
594 path const assetmap_file = dir / "ASSETMAP";
596 auto st = stages.begin();
597 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
598 BOOST_REQUIRE (st->second);
599 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
601 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
602 BOOST_REQUIRE (st->second);
603 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
605 BOOST_CHECK_EQUAL (st->first, "Checking reel");
606 BOOST_REQUIRE (!st->second);
608 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
609 BOOST_REQUIRE (st->second);
610 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
612 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
613 BOOST_REQUIRE (st->second);
614 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
616 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
617 BOOST_REQUIRE (st->second);
618 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
620 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
621 BOOST_REQUIRE (st->second);
622 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
624 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
625 BOOST_REQUIRE (st->second);
626 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
628 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
629 BOOST_REQUIRE (st->second);
630 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
632 BOOST_REQUIRE (st == stages.end());
634 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
635 auto i = notes.begin ();
636 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
637 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
639 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
640 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
643 /* DCP with a short asset */
644 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
646 auto dir = setup (8, "invalid_duration");
647 check_verify_result (
650 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
651 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
652 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
653 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
654 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
662 dcp_from_frame (dcp::ArrayData const& frame, path dir)
664 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
665 create_directories (dir);
666 auto writer = asset->start_write (dir / "pic.mxf", true);
667 for (int i = 0; i < 24; ++i) {
668 writer->write (frame.data(), frame.size());
672 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
673 return write_dcp_with_single_asset (dir, reel_asset);
677 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
679 int const too_big = 1302083 * 2;
681 /* Compress a black image */
682 auto image = black_image ();
683 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
684 BOOST_REQUIRE (frame.size() < too_big);
686 /* Place it in a bigger block with some zero padding at the end */
687 dcp::ArrayData oversized_frame(too_big);
688 memcpy (oversized_frame.data(), frame.data(), frame.size());
689 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
691 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
692 prepare_directory (dir);
693 auto cpl = dcp_from_frame (oversized_frame, dir);
695 check_verify_result (
698 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
699 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
705 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
707 int const nearly_too_big = 1302083 * 0.98;
709 /* Compress a black image */
710 auto image = black_image ();
711 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
712 BOOST_REQUIRE (frame.size() < nearly_too_big);
714 /* Place it in a bigger block with some zero padding at the end */
715 dcp::ArrayData oversized_frame(nearly_too_big);
716 memcpy (oversized_frame.data(), frame.data(), frame.size());
717 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
719 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
720 prepare_directory (dir);
721 auto cpl = dcp_from_frame (oversized_frame, dir);
723 check_verify_result (
726 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
727 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
733 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
735 /* Compress a black image */
736 auto image = black_image ();
737 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
738 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
740 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
741 prepare_directory (dir);
742 auto cpl = dcp_from_frame (frame, dir);
744 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
748 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
750 path const dir("build/test/verify_valid_interop_subtitles");
751 prepare_directory (dir);
752 copy_file ("test/data/subs1.xml", dir / "subs.xml");
753 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
754 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
755 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
757 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
761 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
763 using namespace boost::filesystem;
765 path const dir("build/test/verify_invalid_interop_subtitles");
766 prepare_directory (dir);
767 copy_file ("test/data/subs1.xml", dir / "subs.xml");
768 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
769 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
770 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
773 Editor e (dir / "subs.xml");
774 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
777 check_verify_result (
780 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
781 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
783 dcp::VerificationNote::Type::ERROR,
784 dcp::VerificationNote::Code::INVALID_XML,
785 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
793 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
795 path const dir("build/test/verify_valid_smpte_subtitles");
796 prepare_directory (dir);
797 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
798 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
799 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
800 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
802 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
806 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
808 using namespace boost::filesystem;
810 path const dir("build/test/verify_invalid_smpte_subtitles");
811 prepare_directory (dir);
812 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
813 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
814 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
815 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
816 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
818 check_verify_result (
821 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
823 dcp::VerificationNote::Type::ERROR,
824 dcp::VerificationNote::Code::INVALID_XML,
825 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
829 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
835 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles)
837 path const dir("build/test/verify_empty_text_node_in_subtitles");
838 prepare_directory (dir);
839 copy_file ("test/data/empty_text.mxf", dir / "subs.mxf");
840 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
841 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
842 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
844 check_verify_result (
847 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
848 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
849 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
850 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
855 /** A <Text> node with no content except some <Font> nodes, which themselves do have content */
856 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_child_nodes)
858 path const dir("build/test/verify_empty_text_node_in_subtitles_with_child_nodes");
859 prepare_directory (dir);
860 copy_file ("test/data/empty_but_with_children.xml", dir / "subs.xml");
861 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
862 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
863 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
865 check_verify_result (
868 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
873 /** A <Text> node with no content except some <Font> nodes, which themselves also have no content */
874 BOOST_AUTO_TEST_CASE (verify_empty_text_node_in_subtitles_with_empty_child_nodes)
876 path const dir("build/test/verify_empty_text_node_in_subtitles_with_empty_child_nodes");
877 prepare_directory (dir);
878 copy_file ("test/data/empty_with_empty_children.xml", dir / "subs.xml");
879 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
880 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 192, 0);
881 auto cpl = write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
883 check_verify_result (
886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
887 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EMPTY_TEXT },
892 BOOST_AUTO_TEST_CASE (verify_external_asset)
894 path const ov_dir("build/test/verify_external_asset");
895 prepare_directory (ov_dir);
897 auto image = black_image ();
898 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
899 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
900 dcp_from_frame (frame, ov_dir);
902 dcp::DCP ov (ov_dir);
905 path const vf_dir("build/test/verify_external_asset_vf");
906 prepare_directory (vf_dir);
908 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
909 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
911 check_verify_result (
914 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
920 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
922 path const dir("build/test/verify_valid_cpl_metadata");
923 prepare_directory (dir);
925 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
926 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
927 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
929 auto reel = make_shared<dcp::Reel>();
930 reel->add (reel_asset);
932 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
933 reel->add (simple_markers(16 * 24));
935 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
937 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
938 cpl->set_main_sound_sample_rate (48000);
939 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
940 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
941 cpl->set_version_number (1);
945 dcp.set_annotation_text("hello");
950 path find_cpl (path dir)
952 for (auto i: directory_iterator(dir)) {
953 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
958 BOOST_REQUIRE (false);
963 /* DCP with invalid CompositionMetadataAsset */
964 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
966 using namespace boost::filesystem;
968 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
969 prepare_directory (dir);
971 auto reel = make_shared<dcp::Reel>();
972 reel->add (black_picture_asset(dir));
973 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
975 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
976 cpl->set_main_sound_sample_rate (48000);
977 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
978 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
979 cpl->set_version_number (1);
981 reel->add (simple_markers());
985 dcp.set_annotation_text("hello");
989 Editor e (find_cpl(dir));
990 e.replace ("MainSound", "MainSoundX");
993 check_verify_result (
996 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 50 },
997 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 51 },
999 dcp::VerificationNote::Type::ERROR,
1000 dcp::VerificationNote::Code::INVALID_XML,
1001 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
1002 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
1003 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
1004 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
1005 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
1006 "ExtensionMetadataList?,)'"),
1007 canonical(cpl->file().get()),
1010 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
1015 /* DCP with invalid CompositionMetadataAsset */
1016 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
1018 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
1019 prepare_directory (dir);
1021 auto reel = make_shared<dcp::Reel>();
1022 reel->add (black_picture_asset(dir));
1023 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1025 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1026 cpl->set_main_sound_sample_rate (48000);
1027 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1028 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1032 dcp.set_annotation_text("hello");
1036 Editor e (find_cpl(dir));
1037 e.replace ("meta:Width", "meta:WidthX");
1040 check_verify_result (
1042 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
1047 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
1049 path const dir("build/test/verify_invalid_language1");
1050 prepare_directory (dir);
1051 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1052 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1053 asset->_language = "wrong-andbad";
1054 asset->write (dir / "subs.mxf");
1055 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1056 reel_asset->_language = "badlang";
1057 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1059 check_verify_result (
1062 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1063 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1069 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
1070 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
1072 path const dir("build/test/verify_invalid_language2");
1073 prepare_directory (dir);
1074 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
1075 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
1076 asset->_language = "wrong-andbad";
1077 asset->write (dir / "subs.mxf");
1078 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
1079 reel_asset->_language = "badlang";
1080 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1082 check_verify_result (
1085 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
1086 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
1087 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1092 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
1093 * the release territory.
1095 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
1097 path const dir("build/test/verify_invalid_language3");
1098 prepare_directory (dir);
1100 auto picture = simple_picture (dir, "foo");
1101 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1102 auto reel = make_shared<dcp::Reel>();
1103 reel->add (reel_picture);
1104 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
1105 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1106 reel->add (reel_sound);
1107 reel->add (simple_markers());
1109 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1111 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
1112 cpl->_additional_subtitle_languages.push_back("andso-is-this");
1113 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1114 cpl->set_main_sound_sample_rate (48000);
1115 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1116 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
1117 cpl->set_version_number (1);
1118 cpl->_release_territory = "fred-jim";
1119 auto dcp = make_shared<dcp::DCP>(dir);
1121 dcp->set_annotation_text("hello");
1124 check_verify_result (
1127 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
1128 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
1129 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
1130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
1136 vector<dcp::VerificationNote>
1137 check_picture_size (int width, int height, int frame_rate, bool three_d)
1139 using namespace boost::filesystem;
1141 path dcp_path = "build/test/verify_picture_test";
1142 prepare_directory (dcp_path);
1144 shared_ptr<dcp::PictureAsset> mp;
1146 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1148 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1150 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1152 auto image = black_image (dcp::Size(width, height));
1153 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1154 int const length = three_d ? frame_rate * 2 : frame_rate;
1155 for (int i = 0; i < length; ++i) {
1156 picture_writer->write (j2c.data(), j2c.size());
1158 picture_writer->finalize ();
1160 auto d = make_shared<dcp::DCP>(dcp_path);
1161 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1162 cpl->set_annotation_text ("A Test DCP");
1163 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1164 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1165 cpl->set_main_sound_sample_rate (48000);
1166 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1167 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1168 cpl->set_version_number (1);
1170 auto reel = make_shared<dcp::Reel>();
1173 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1175 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1178 reel->add (simple_markers(frame_rate));
1183 d->set_annotation_text("A Test DCP");
1186 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1192 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1194 auto notes = check_picture_size(width, height, frame_rate, three_d);
1195 BOOST_CHECK_EQUAL (notes.size(), 0U);
1201 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1203 auto notes = check_picture_size(width, height, frame_rate, three_d);
1204 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1205 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1206 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1212 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1214 auto notes = check_picture_size(width, height, frame_rate, three_d);
1215 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1216 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1217 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1223 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1225 auto notes = check_picture_size(width, height, frame_rate, three_d);
1226 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1227 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1228 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1232 BOOST_AUTO_TEST_CASE (verify_picture_size)
1234 using namespace boost::filesystem;
1237 check_picture_size_ok (2048, 858, 24, false);
1238 check_picture_size_ok (2048, 858, 25, false);
1239 check_picture_size_ok (2048, 858, 48, false);
1240 check_picture_size_ok (2048, 858, 24, true);
1241 check_picture_size_ok (2048, 858, 25, true);
1242 check_picture_size_ok (2048, 858, 48, true);
1245 check_picture_size_ok (1998, 1080, 24, false);
1246 check_picture_size_ok (1998, 1080, 25, false);
1247 check_picture_size_ok (1998, 1080, 48, false);
1248 check_picture_size_ok (1998, 1080, 24, true);
1249 check_picture_size_ok (1998, 1080, 25, true);
1250 check_picture_size_ok (1998, 1080, 48, true);
1253 check_picture_size_ok (4096, 1716, 24, false);
1256 check_picture_size_ok (3996, 2160, 24, false);
1258 /* Bad frame size */
1259 check_picture_size_bad_frame_size (2050, 858, 24, false);
1260 check_picture_size_bad_frame_size (2048, 658, 25, false);
1261 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1262 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1264 /* Bad 2K frame rate */
1265 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1266 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1267 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1269 /* Bad 4K frame rate */
1270 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1271 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1274 auto notes = check_picture_size(3996, 2160, 24, true);
1275 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1276 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1277 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1283 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")
1286 std::make_shared<dcp::SubtitleString>(
1294 dcp::Time(start_frame, 24, 24),
1295 dcp::Time(end_frame, 24, 24),
1297 dcp::HAlign::CENTER,
1301 dcp::Direction::LTR,
1313 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1315 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1316 prepare_directory (dir);
1318 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1319 for (int i = 0; i < 2048; ++i) {
1320 add_test_subtitle (asset, i * 24, i * 24 + 20);
1322 asset->set_language (dcp::LanguageTag("de-DE"));
1323 asset->write (dir / "subs.mxf");
1324 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1325 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1327 check_verify_result (
1330 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1332 dcp::VerificationNote::Type::BV21_ERROR,
1333 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1335 canonical(dir / "subs.mxf")
1337 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1344 shared_ptr<dcp::SMPTESubtitleAsset>
1345 make_large_subtitle_asset (path font_file)
1347 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1348 dcp::ArrayData big_fake_font(1024 * 1024);
1349 big_fake_font.write (font_file);
1350 for (int i = 0; i < 116; ++i) {
1351 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1359 verify_timed_text_asset_too_large (string name)
1361 auto const dir = path("build/test") / name;
1362 prepare_directory (dir);
1363 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1364 add_test_subtitle (asset, 0, 240);
1365 asset->set_language (dcp::LanguageTag("de-DE"));
1366 asset->write (dir / "subs.mxf");
1368 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1369 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1371 check_verify_result (
1374 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121695542"), canonical(dir / "subs.mxf") },
1375 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1377 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1378 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1383 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1385 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1386 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1390 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1392 path dir = "build/test/verify_missing_subtitle_language";
1393 prepare_directory (dir);
1394 auto dcp = make_simple (dir, 1, 106);
1397 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1398 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1399 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1400 "<ContentTitleText>Content</ContentTitleText>"
1401 "<AnnotationText>Annotation</AnnotationText>"
1402 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1403 "<ReelNumber>1</ReelNumber>"
1404 "<EditRate>24 1</EditRate>"
1405 "<TimeCodeRate>24</TimeCodeRate>"
1406 "<StartTime>00:00:00:00</StartTime>"
1407 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1409 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1410 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1411 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1417 dcp::File xml_file(dir / "subs.xml", "w");
1418 BOOST_REQUIRE (xml_file);
1419 xml_file.write(xml.c_str(), xml.size(), 1);
1421 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1422 subs->write (dir / "subs.mxf");
1424 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1425 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1426 dcp->set_annotation_text("A Test DCP");
1429 check_verify_result (
1432 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1433 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1438 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1440 path path ("build/test/verify_mismatched_subtitle_languages");
1441 auto constexpr reel_length = 192;
1442 auto dcp = make_simple (path, 2, reel_length);
1443 auto cpl = dcp->cpls()[0];
1446 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1447 subs->set_language (dcp::LanguageTag("de-DE"));
1448 subs->add (simple_subtitle());
1449 subs->write (path / "subs1.mxf");
1450 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1451 cpl->reels()[0]->add(reel_subs);
1455 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1456 subs->set_language (dcp::LanguageTag("en-US"));
1457 subs->add (simple_subtitle());
1458 subs->write (path / "subs2.mxf");
1459 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1460 cpl->reels()[1]->add(reel_subs);
1463 dcp->set_annotation_text("A Test DCP");
1466 check_verify_result (
1469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1470 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1471 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1476 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1478 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1479 auto constexpr reel_length = 192;
1480 auto dcp = make_simple (path, 2, reel_length);
1481 auto cpl = dcp->cpls()[0];
1484 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1485 ccaps->set_language (dcp::LanguageTag("de-DE"));
1486 ccaps->add (simple_subtitle());
1487 ccaps->write (path / "subs1.mxf");
1488 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1489 cpl->reels()[0]->add(reel_ccaps);
1493 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1494 ccaps->set_language (dcp::LanguageTag("en-US"));
1495 ccaps->add (simple_subtitle());
1496 ccaps->write (path / "subs2.mxf");
1497 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1498 cpl->reels()[1]->add(reel_ccaps);
1501 dcp->set_annotation_text("A Test DCP");
1504 check_verify_result (
1507 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1508 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1513 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1515 path dir = "build/test/verify_missing_subtitle_start_time";
1516 prepare_directory (dir);
1517 auto dcp = make_simple (dir, 1, 106);
1520 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1521 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1522 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1523 "<ContentTitleText>Content</ContentTitleText>"
1524 "<AnnotationText>Annotation</AnnotationText>"
1525 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1526 "<ReelNumber>1</ReelNumber>"
1527 "<Language>de-DE</Language>"
1528 "<EditRate>24 1</EditRate>"
1529 "<TimeCodeRate>24</TimeCodeRate>"
1530 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1532 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1533 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1534 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1540 dcp::File xml_file(dir / "subs.xml", "w");
1541 BOOST_REQUIRE (xml_file);
1542 xml_file.write(xml.c_str(), xml.size(), 1);
1544 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1545 subs->write (dir / "subs.mxf");
1547 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1548 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1549 dcp->set_annotation_text("A Test DCP");
1552 check_verify_result (
1555 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1556 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1561 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1563 path dir = "build/test/verify_invalid_subtitle_start_time";
1564 prepare_directory (dir);
1565 auto dcp = make_simple (dir, 1, 106);
1568 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1569 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1570 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1571 "<ContentTitleText>Content</ContentTitleText>"
1572 "<AnnotationText>Annotation</AnnotationText>"
1573 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1574 "<ReelNumber>1</ReelNumber>"
1575 "<Language>de-DE</Language>"
1576 "<EditRate>24 1</EditRate>"
1577 "<TimeCodeRate>24</TimeCodeRate>"
1578 "<StartTime>00:00:02:00</StartTime>"
1579 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1581 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1582 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1583 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1589 dcp::File xml_file(dir / "subs.xml", "w");
1590 BOOST_REQUIRE (xml_file);
1591 xml_file.write(xml.c_str(), xml.size(), 1);
1593 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1594 subs->write (dir / "subs.mxf");
1596 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1597 dcp->cpls().front()->reels().front()->add(reel_subs);
1598 dcp->set_annotation_text("A Test DCP");
1601 check_verify_result (
1604 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1605 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1613 TestText (int in_, int out_, float v_position_ = 0, dcp::VAlign v_align_ = dcp::VAlign::CENTER, string text_ = "Hello")
1616 , v_position(v_position_)
1624 dcp::VAlign v_align;
1630 shared_ptr<dcp::CPL>
1631 dcp_with_text (path dir, vector<TestText> subs)
1633 prepare_directory (dir);
1634 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1635 asset->set_start_time (dcp::Time());
1636 for (auto i: subs) {
1637 add_test_subtitle (asset, i.in, i.out, i.v_position, i.v_align, i.text);
1639 asset->set_language (dcp::LanguageTag("de-DE"));
1640 asset->write (dir / "subs.mxf");
1642 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1643 return write_dcp_with_single_asset (dir, reel_asset);
1648 shared_ptr<dcp::CPL>
1649 dcp_with_text_from_file (path dir, boost::filesystem::path subs_xml)
1651 prepare_directory (dir);
1652 auto asset = make_shared<dcp::SMPTESubtitleAsset>(subs_xml);
1653 asset->set_start_time (dcp::Time());
1654 asset->set_language (dcp::LanguageTag("de-DE"));
1656 auto subs_mxf = dir / "subs.mxf";
1657 asset->write (subs_mxf);
1659 /* The call to write() puts the asset into the DCP correctly but it will have
1660 * XML re-written by our parser. Overwrite the MXF using the given file's verbatim
1663 ASDCP::TimedText::MXFWriter writer;
1664 ASDCP::WriterInfo writer_info;
1665 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
1667 Kumu::hex2bin (asset->id().c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
1668 DCP_ASSERT (c == Kumu::UUID_Length);
1669 ASDCP::TimedText::TimedTextDescriptor descriptor;
1670 descriptor.ContainerDuration = asset->intrinsic_duration();
1671 Kumu::hex2bin (asset->xml_id()->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
1672 DCP_ASSERT (c == Kumu::UUID_Length);
1673 ASDCP::Result_t r = writer.OpenWrite (subs_mxf.string().c_str(), writer_info, descriptor, 16384);
1674 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1675 r = writer.WriteTimedTextResource (dcp::file_to_string(subs_xml));
1676 BOOST_REQUIRE (!ASDCP_FAILURE(r));
1679 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1680 return write_dcp_with_single_asset (dir, reel_asset);
1684 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1686 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1687 /* Just too early */
1688 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1689 check_verify_result (
1692 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1699 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1701 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1702 /* Just late enough */
1703 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1704 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1708 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1710 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1711 prepare_directory (dir);
1713 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1714 asset1->set_start_time (dcp::Time());
1715 /* Just late enough */
1716 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1717 asset1->set_language (dcp::LanguageTag("de-DE"));
1718 asset1->write (dir / "subs1.mxf");
1719 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1720 auto reel1 = make_shared<dcp::Reel>();
1721 reel1->add (reel_asset1);
1722 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24);
1723 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1724 reel1->add (markers1);
1726 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1727 asset2->set_start_time (dcp::Time());
1728 /* This would be too early on first reel but should be OK on the second */
1729 add_test_subtitle (asset2, 3, 4 * 24);
1730 asset2->set_language (dcp::LanguageTag("de-DE"));
1731 asset2->write (dir / "subs2.mxf");
1732 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1733 auto reel2 = make_shared<dcp::Reel>();
1734 reel2->add (reel_asset2);
1735 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24);
1736 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1737 reel2->add (markers2);
1739 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1742 auto dcp = make_shared<dcp::DCP>(dir);
1744 dcp->set_annotation_text("hello");
1747 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1751 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1753 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1754 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1758 { 5 * 24 + 1, 6 * 24 },
1760 check_verify_result (
1763 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1764 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1769 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1771 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1772 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1776 { 5 * 24 + 16, 8 * 24 },
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_invalid_subtitle_duration)
1784 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1785 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1786 check_verify_result (
1789 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1790 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1795 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1797 auto const dir = path("build/test/verify_valid_subtitle_duration");
1798 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1799 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1803 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1805 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1806 prepare_directory (dir);
1807 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1808 asset->set_start_time (dcp::Time());
1809 add_test_subtitle (asset, 0, 4 * 24);
1810 asset->set_language (dcp::LanguageTag("de-DE"));
1811 asset->write (dir / "subs.mxf");
1813 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1814 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1815 check_verify_result (
1818 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1819 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1827 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1829 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1830 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1833 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1834 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1835 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1836 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1838 check_verify_result (
1841 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1842 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1847 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1849 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1850 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1853 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1854 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1855 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1857 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1861 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1863 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1864 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1867 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1868 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1869 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1870 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1872 check_verify_result (
1875 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1876 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1881 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1883 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1884 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1887 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1888 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1889 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1890 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1892 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1896 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1898 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1899 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1902 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123" }
1904 check_verify_result (
1907 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1908 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1913 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1915 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1916 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1919 { 96, 300, 0.0, dcp::VAlign::CENTER, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1921 check_verify_result (
1924 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1925 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1930 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1932 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1933 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1936 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1937 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1938 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1939 { 96, 200, 0.3, dcp::VAlign::CENTER, "lines" }
1941 check_verify_result (
1944 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1945 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1950 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1952 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1953 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1956 { 96, 200, 0.0, dcp::VAlign::CENTER, "We" },
1957 { 96, 200, 0.1, dcp::VAlign::CENTER, "have" },
1958 { 96, 200, 0.2, dcp::VAlign::CENTER, "four" },
1960 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1964 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1966 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1967 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1970 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1971 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1972 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1973 { 150, 180, 0.3, dcp::VAlign::CENTER, "lines" }
1975 check_verify_result (
1978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1984 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1986 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1987 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1990 { 96, 300, 0.0, dcp::VAlign::CENTER, "We" },
1991 { 96, 300, 0.1, dcp::VAlign::CENTER, "have" },
1992 { 150, 180, 0.2, dcp::VAlign::CENTER, "four" },
1993 { 190, 250, 0.3, dcp::VAlign::CENTER, "lines" }
1995 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1999 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_length)
2001 auto const dir = path ("build/test/verify_valid_closed_caption_line_length");
2002 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2005 { 96, 300, 0.0, dcp::VAlign::CENTER, "01234567890123456789012345678901" }
2007 check_verify_result (
2010 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2015 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
2017 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
2018 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2021 { 96, 300, 0.0, dcp::VAlign::CENTER, "0123456789012345678901234567890123" }
2023 check_verify_result (
2026 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
2027 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2032 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign1)
2034 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign1");
2035 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2038 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2039 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2040 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2042 check_verify_result (
2045 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2050 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_valign2)
2052 auto const dir = path ("build/test/verify_mismatched_closed_caption_valign2");
2053 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2056 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2057 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2058 { 96, 300, 0.2, dcp::VAlign::CENTER, "not fine" },
2060 check_verify_result (
2063 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_VALIGN },
2064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2069 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering1)
2071 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering1");
2072 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2075 { 96, 300, 0.0, dcp::VAlign::TOP, "This" },
2076 { 96, 300, 0.1, dcp::VAlign::TOP, "is" },
2077 { 96, 300, 0.2, dcp::VAlign::TOP, "fine" },
2079 check_verify_result (
2082 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2087 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering2)
2089 auto const dir = path ("build/test/verify_invalid_incorrect_closed_caption_ordering2");
2090 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
2093 { 96, 300, 0.2, dcp::VAlign::BOTTOM, "This" },
2094 { 96, 300, 0.1, dcp::VAlign::BOTTOM, "is" },
2095 { 96, 300, 0.0, dcp::VAlign::BOTTOM, "also fine" },
2097 check_verify_result (
2100 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2105 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering3)
2107 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering3");
2108 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering3.xml");
2109 check_verify_result (
2112 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ORDERING },
2113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2118 BOOST_AUTO_TEST_CASE (verify_incorrect_closed_caption_ordering4)
2120 auto const dir = path ("build/test/verify_incorrect_closed_caption_ordering4");
2121 auto cpl = dcp_with_text_from_file<dcp::ReelSMPTEClosedCaptionAsset> (dir, "test/data/verify_incorrect_closed_caption_ordering4.xml");
2122 check_verify_result (
2125 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2131 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
2133 path const dir("build/test/verify_invalid_sound_frame_rate");
2134 prepare_directory (dir);
2136 auto picture = simple_picture (dir, "foo");
2137 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
2138 auto reel = make_shared<dcp::Reel>();
2139 reel->add (reel_picture);
2140 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000, boost::none);
2141 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
2142 reel->add (reel_sound);
2143 reel->add (simple_markers());
2144 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2146 auto dcp = make_shared<dcp::DCP>(dir);
2148 dcp->set_annotation_text("hello");
2151 check_verify_result (
2154 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
2155 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
2160 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
2162 path const dir("build/test/verify_missing_cpl_annotation_text");
2163 auto dcp = make_simple (dir);
2164 dcp->set_annotation_text("A Test DCP");
2167 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2169 auto const cpl = dcp->cpls()[0];
2172 BOOST_REQUIRE (cpl->file());
2173 Editor e(cpl->file().get());
2174 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
2177 check_verify_result (
2180 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2181 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2186 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
2188 path const dir("build/test/verify_mismatched_cpl_annotation_text");
2189 auto dcp = make_simple (dir);
2190 dcp->set_annotation_text("A Test DCP");
2193 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2194 auto const cpl = dcp->cpls()[0];
2197 BOOST_REQUIRE (cpl->file());
2198 Editor e(cpl->file().get());
2199 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
2202 check_verify_result (
2205 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
2206 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
2211 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
2213 path const dir("build/test/verify_mismatched_asset_duration");
2214 prepare_directory (dir);
2215 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
2216 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2218 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
2219 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
2221 auto reel = make_shared<dcp::Reel>(
2222 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2223 make_shared<dcp::ReelSoundAsset>(ms, 0)
2226 reel->add (simple_markers());
2230 dcp->set_annotation_text("A Test DCP");
2233 check_verify_result (
2236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
2237 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2244 shared_ptr<dcp::CPL>
2245 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2247 prepare_directory (dir);
2248 auto dcp = make_shared<dcp::DCP>(dir);
2249 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2251 auto constexpr reel_length = 192;
2253 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2254 subs->set_language (dcp::LanguageTag("de-DE"));
2255 subs->set_start_time (dcp::Time());
2256 subs->add (simple_subtitle());
2257 subs->write (dir / "subs.mxf");
2258 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2260 auto reel1 = make_shared<dcp::Reel>(
2261 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2262 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2266 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2269 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2270 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2271 reel1->add (markers1);
2275 auto reel2 = make_shared<dcp::Reel>(
2276 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2277 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2281 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2284 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2285 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2286 reel2->add (markers2);
2291 dcp->set_annotation_text("A Test DCP");
2298 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2301 path dir ("build/test/missing_main_subtitle_from_some_reels");
2302 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2303 check_verify_result (
2306 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2307 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2313 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2314 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2315 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2319 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2320 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2321 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2327 shared_ptr<dcp::CPL>
2328 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2330 prepare_directory (dir);
2331 auto dcp = make_shared<dcp::DCP>(dir);
2332 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2334 auto constexpr reel_length = 192;
2336 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2337 subs->set_language (dcp::LanguageTag("de-DE"));
2338 subs->set_start_time (dcp::Time());
2339 subs->add (simple_subtitle());
2340 subs->write (dir / "subs.mxf");
2342 auto reel1 = make_shared<dcp::Reel>(
2343 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2344 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2347 for (int i = 0; i < caps_in_reel1; ++i) {
2348 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2351 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2352 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2353 reel1->add (markers1);
2357 auto reel2 = make_shared<dcp::Reel>(
2358 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2359 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2362 for (int i = 0; i < caps_in_reel2; ++i) {
2363 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2366 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length);
2367 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2368 reel2->add (markers2);
2373 dcp->set_annotation_text("A Test DCP");
2380 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2383 path dir ("build/test/mismatched_closed_caption_asset_counts");
2384 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2385 check_verify_result (
2388 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2389 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2394 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2395 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2396 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2400 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2401 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2402 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2409 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2411 prepare_directory (dir);
2412 auto dcp = make_shared<dcp::DCP>(dir);
2413 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2415 auto constexpr reel_length = 192;
2417 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2418 subs->set_language (dcp::LanguageTag("de-DE"));
2419 subs->set_start_time (dcp::Time());
2420 subs->add (simple_subtitle());
2421 subs->write (dir / "subs.mxf");
2422 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2425 auto reel = make_shared<dcp::Reel>(
2426 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2427 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2430 reel->add (reel_text);
2432 reel->add (simple_markers(reel_length));
2437 dcp->set_annotation_text("A Test DCP");
2440 check_verify_result (
2443 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2444 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2449 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2451 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2452 "build/test/verify_subtitle_entry_point_must_be_present",
2453 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2454 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2455 asset->unset_entry_point ();
2459 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2460 "build/test/verify_subtitle_entry_point_must_be_zero",
2461 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2462 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2463 asset->set_entry_point (4);
2467 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2468 "build/test/verify_closed_caption_entry_point_must_be_present",
2469 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2470 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2471 asset->unset_entry_point ();
2475 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2476 "build/test/verify_closed_caption_entry_point_must_be_zero",
2477 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2478 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2479 asset->set_entry_point (9);
2485 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2489 path const dir("build/test/verify_missing_hash");
2490 auto dcp = make_simple (dir);
2491 dcp->set_annotation_text("A Test DCP");
2494 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2495 auto const cpl = dcp->cpls()[0];
2496 BOOST_REQUIRE_EQUAL (cpl->reels().size(), 1U);
2497 BOOST_REQUIRE (cpl->reels()[0]->main_picture());
2498 auto asset_id = cpl->reels()[0]->main_picture()->id();
2501 BOOST_REQUIRE (cpl->file());
2502 Editor e(cpl->file().get());
2503 e.delete_first_line_containing("<Hash>");
2506 check_verify_result (
2509 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2510 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, asset_id }
2517 verify_markers_test (
2519 vector<pair<dcp::Marker, dcp::Time>> markers,
2520 vector<dcp::VerificationNote> test_notes
2523 auto dcp = make_simple (dir);
2524 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2525 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24);
2526 for (auto const& i: markers) {
2527 markers_asset->set (i.first, i.second);
2529 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2530 dcp->set_annotation_text("A Test DCP");
2533 check_verify_result ({dir}, test_notes);
2537 BOOST_AUTO_TEST_CASE (verify_markers)
2539 verify_markers_test (
2540 "build/test/verify_markers_all_correct",
2542 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2543 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2544 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2545 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2550 verify_markers_test (
2551 "build/test/verify_markers_missing_ffec",
2553 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2554 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2555 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2558 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2561 verify_markers_test (
2562 "build/test/verify_markers_missing_ffmc",
2564 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2565 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2566 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2569 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2572 verify_markers_test (
2573 "build/test/verify_markers_missing_ffoc",
2575 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2576 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2577 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2580 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2583 verify_markers_test (
2584 "build/test/verify_markers_missing_lfoc",
2586 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2587 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2588 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2591 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2594 verify_markers_test (
2595 "build/test/verify_markers_incorrect_ffoc",
2597 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2598 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2599 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2600 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2603 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2606 verify_markers_test (
2607 "build/test/verify_markers_incorrect_lfoc",
2609 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2610 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2611 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2612 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2615 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2620 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2622 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2623 prepare_directory (dir);
2624 auto dcp = make_simple (dir);
2625 auto cpl = dcp->cpls()[0];
2626 cpl->unset_version_number();
2627 dcp->set_annotation_text("A Test DCP");
2630 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2634 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2636 path dir = "build/test/verify_missing_extension_metadata1";
2637 auto dcp = make_simple (dir);
2638 dcp->set_annotation_text("A Test DCP");
2641 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2642 auto cpl = dcp->cpls()[0];
2645 Editor e (cpl->file().get());
2646 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2649 check_verify_result (
2652 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2653 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2658 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2660 path dir = "build/test/verify_missing_extension_metadata2";
2661 auto dcp = make_simple (dir);
2662 dcp->set_annotation_text("A Test DCP");
2665 auto cpl = dcp->cpls()[0];
2668 Editor e (cpl->file().get());
2669 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2672 check_verify_result (
2675 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2681 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2683 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2684 auto dcp = make_simple (dir);
2685 dcp->set_annotation_text("A Test DCP");
2688 auto const cpl = dcp->cpls()[0];
2691 Editor e (cpl->file().get());
2692 e.replace ("<meta:Name>A", "<meta:NameX>A");
2693 e.replace ("n</meta:Name>", "n</meta:NameX>");
2696 check_verify_result (
2699 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 70 },
2700 { 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 },
2701 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2706 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2708 path dir = "build/test/verify_invalid_extension_metadata1";
2709 auto dcp = make_simple (dir);
2710 dcp->set_annotation_text("A Test DCP");
2713 auto cpl = dcp->cpls()[0];
2716 Editor e (cpl->file().get());
2717 e.replace ("Application", "Fred");
2720 check_verify_result (
2723 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2724 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2729 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2731 path dir = "build/test/verify_invalid_extension_metadata2";
2732 auto dcp = make_simple (dir);
2733 dcp->set_annotation_text("A Test DCP");
2736 auto cpl = dcp->cpls()[0];
2739 Editor e (cpl->file().get());
2740 e.replace ("DCP Constraints Profile", "Fred");
2743 check_verify_result (
2746 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2747 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2752 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2754 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2755 auto dcp = make_simple (dir);
2756 dcp->set_annotation_text("A Test DCP");
2759 auto const cpl = dcp->cpls()[0];
2762 Editor e (cpl->file().get());
2763 e.replace ("<meta:Value>", "<meta:ValueX>");
2764 e.replace ("</meta:Value>", "</meta:ValueX>");
2767 check_verify_result (
2770 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 74 },
2771 { 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 },
2772 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2777 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2779 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2780 auto dcp = make_simple (dir);
2781 dcp->set_annotation_text("A Test DCP");
2784 auto const cpl = dcp->cpls()[0];
2787 Editor e (cpl->file().get());
2788 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2791 check_verify_result (
2794 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2795 { 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() },
2800 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2802 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2803 auto dcp = make_simple (dir);
2804 dcp->set_annotation_text("A Test DCP");
2807 auto const cpl = dcp->cpls()[0];
2810 Editor e (cpl->file().get());
2811 e.replace ("<meta:Property>", "<meta:PropertyX>");
2812 e.replace ("</meta:Property>", "</meta:PropertyX>");
2815 check_verify_result (
2818 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 72 },
2819 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 76 },
2820 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2825 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2827 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2828 auto dcp = make_simple (dir);
2829 dcp->set_annotation_text("A Test DCP");
2832 auto const cpl = dcp->cpls()[0];
2835 Editor e (cpl->file().get());
2836 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2837 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2840 check_verify_result (
2843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 71 },
2844 { 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 },
2845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2851 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2853 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2854 prepare_directory (dir);
2855 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2856 copy_file (i.path(), dir / i.path().filename());
2859 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2860 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2864 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2867 check_verify_result (
2870 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2871 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2872 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2873 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2874 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2875 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2876 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2877 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2882 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2884 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2885 prepare_directory (dir);
2886 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2887 copy_file (i.path(), dir / i.path().filename());
2890 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2891 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2894 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2897 check_verify_result (
2900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2903 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2904 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2905 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2906 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2911 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2913 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2914 prepare_directory (dir);
2915 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2916 copy_file (i.path(), dir / i.path().filename());
2920 Editor e (dir / dcp_test1_pkl);
2921 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2924 check_verify_result ({dir}, {});
2928 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2930 path dir ("build/test/verify_must_not_be_partially_encrypted");
2931 prepare_directory (dir);
2935 auto signer = make_shared<dcp::CertificateChain>();
2936 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2937 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2938 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2939 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2941 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2945 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2948 auto writer = mp->start_write (dir / "video.mxf", false);
2949 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2950 for (int i = 0; i < 24; ++i) {
2951 writer->write (j2c.data(), j2c.size());
2953 writer->finalize ();
2955 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2957 auto reel = make_shared<dcp::Reel>(
2958 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2959 make_shared<dcp::ReelSoundAsset>(ms, 0)
2962 reel->add (simple_markers());
2966 cpl->set_content_version (
2967 {"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"}
2969 cpl->set_annotation_text ("A Test DCP");
2970 cpl->set_issuer ("OpenDCP 0.0.25");
2971 cpl->set_creator ("OpenDCP 0.0.25");
2972 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2973 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2974 cpl->set_main_sound_sample_rate (48000);
2975 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2976 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2977 cpl->set_version_number (1);
2981 d.set_issuer("OpenDCP 0.0.25");
2982 d.set_creator("OpenDCP 0.0.25");
2983 d.set_issue_date("2012-07-17T04:45:18+00:00");
2984 d.set_annotation_text("A Test DCP");
2985 d.write_xml(signer);
2987 check_verify_result (
2990 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2995 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2997 vector<dcp::VerificationNote> notes;
2998 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"));
2999 auto reader = picture.start_read ();
3000 auto frame = reader->get_frame (0);
3001 verify_j2k (frame, notes);
3002 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3006 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
3008 vector<dcp::VerificationNote> notes;
3009 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
3010 auto reader = picture.start_read ();
3011 auto frame = reader->get_frame (0);
3012 verify_j2k (frame, notes);
3013 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3017 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
3019 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
3020 prepare_directory (dir);
3021 auto dcp = make_simple (dir);
3023 vector<dcp::VerificationNote> notes;
3024 dcp::MonoPictureAsset picture (find_file(dir, "video"));
3025 auto reader = picture.start_read ();
3026 auto frame = reader->get_frame (0);
3027 verify_j2k (frame, notes);
3028 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
3032 /** Check that ResourceID and the XML ID being different is spotted */
3033 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
3035 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
3036 prepare_directory (dir);
3038 ASDCP::WriterInfo writer_info;
3039 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3042 auto mxf_id = dcp::make_uuid ();
3043 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3044 BOOST_REQUIRE (c == Kumu::UUID_Length);
3046 auto resource_id = dcp::make_uuid ();
3047 ASDCP::TimedText::TimedTextDescriptor descriptor;
3048 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3049 DCP_ASSERT (c == Kumu::UUID_Length);
3051 auto xml_id = dcp::make_uuid ();
3052 ASDCP::TimedText::MXFWriter writer;
3053 auto subs_mxf = dir / "subs.mxf";
3054 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3055 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3056 writer.WriteTimedTextResource (dcp::String::compose(
3057 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3058 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3059 "<Id>urn:uuid:%1</Id>"
3060 "<ContentTitleText>Content</ContentTitleText>"
3061 "<AnnotationText>Annotation</AnnotationText>"
3062 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3063 "<ReelNumber>1</ReelNumber>"
3064 "<Language>en-US</Language>"
3065 "<EditRate>25 1</EditRate>"
3066 "<TimeCodeRate>25</TimeCodeRate>"
3067 "<StartTime>00:00:00:00</StartTime>"
3069 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3070 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3071 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3080 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3081 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3083 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3085 check_verify_result (
3088 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3089 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
3090 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3091 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3096 /** Check that ResourceID and the MXF ID being the same is spotted */
3097 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
3099 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
3100 prepare_directory (dir);
3102 ASDCP::WriterInfo writer_info;
3103 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
3106 auto mxf_id = dcp::make_uuid ();
3107 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
3108 BOOST_REQUIRE (c == Kumu::UUID_Length);
3110 auto resource_id = mxf_id;
3111 ASDCP::TimedText::TimedTextDescriptor descriptor;
3112 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
3113 DCP_ASSERT (c == Kumu::UUID_Length);
3115 auto xml_id = resource_id;
3116 ASDCP::TimedText::MXFWriter writer;
3117 auto subs_mxf = dir / "subs.mxf";
3118 auto r = writer.OpenWrite(subs_mxf.string().c_str(), writer_info, descriptor, 4096);
3119 BOOST_REQUIRE (ASDCP_SUCCESS(r));
3120 writer.WriteTimedTextResource (dcp::String::compose(
3121 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
3122 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
3123 "<Id>urn:uuid:%1</Id>"
3124 "<ContentTitleText>Content</ContentTitleText>"
3125 "<AnnotationText>Annotation</AnnotationText>"
3126 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
3127 "<ReelNumber>1</ReelNumber>"
3128 "<Language>en-US</Language>"
3129 "<EditRate>25 1</EditRate>"
3130 "<TimeCodeRate>25</TimeCodeRate>"
3131 "<StartTime>00:00:00:00</StartTime>"
3133 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
3134 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
3135 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
3144 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
3145 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
3147 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
3149 check_verify_result (
3152 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
3153 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
3154 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
3155 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
3160 /** Check a DCP with a 3D asset marked as 2D */
3161 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
3163 check_verify_result (
3164 { private_test / "data" / "xm" },
3167 dcp::VerificationNote::Type::WARNING,
3168 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
3171 dcp::VerificationNote::Type::BV21_ERROR,
3172 dcp::VerificationNote::Code::INVALID_STANDARD
3179 BOOST_AUTO_TEST_CASE (verify_unexpected_things_in_main_markers)
3181 path dir = "build/test/verify_unexpected_things_in_main_markers";
3182 prepare_directory (dir);
3183 auto dcp = make_simple (dir, 1, 24);
3184 dcp->set_annotation_text("A Test DCP");
3188 Editor e (find_cpl(dir));
3190 " <IntrinsicDuration>24</IntrinsicDuration>",
3191 "<EntryPoint>0</EntryPoint><Duration>24</Duration>"
3195 dcp::CPL cpl (find_cpl(dir));
3197 check_verify_result (
3200 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3201 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_ENTRY_POINT },
3202 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::UNEXPECTED_DURATION },
3207 BOOST_AUTO_TEST_CASE(verify_invalid_content_kind)
3209 path dir = "build/test/verify_invalid_content_kind";
3210 prepare_directory (dir);
3211 auto dcp = make_simple (dir, 1, 24);
3212 dcp->set_annotation_text("A Test DCP");
3216 Editor e(find_cpl(dir));
3217 e.replace("trailer", "trip");
3220 dcp::CPL cpl (find_cpl(dir));
3222 check_verify_result (
3225 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },
3226 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_CONTENT_KIND, string("trip") }
3232 BOOST_AUTO_TEST_CASE(verify_valid_content_kind)
3234 path dir = "build/test/verify_valid_content_kind";
3235 prepare_directory (dir);
3236 auto dcp = make_simple (dir, 1, 24);
3237 dcp->set_annotation_text("A Test DCP");
3241 Editor e(find_cpl(dir));
3242 e.replace("<ContentKind>trailer</ContentKind>", "<ContentKind scope=\"http://bobs.contents/\">trip</ContentKind>");
3245 dcp::CPL cpl (find_cpl(dir));
3247 check_verify_result (
3250 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl.id(), canonical(find_cpl(dir)) },