2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_closed_caption_asset.h"
45 #include "reel_markers_asset.h"
46 #include "reel_mono_picture_asset.h"
47 #include "reel_sound_asset.h"
48 #include "reel_stereo_picture_asset.h"
49 #include "reel_subtitle_asset.h"
50 #include "smpte_subtitle_asset.h"
51 #include "stereo_picture_asset.h"
52 #include "stream_operators.h"
56 #include "verify_j2k.h"
57 #include <boost/test/unit_test.hpp>
58 #include <boost/algorithm/string.hpp>
67 using std::make_shared;
68 using boost::optional;
69 using namespace boost::filesystem;
70 using std::shared_ptr;
73 static list<pair<string, optional<path>>> stages;
74 static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml";
75 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
76 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
79 stage (string s, optional<path> p)
81 stages.push_back (make_pair (s, p));
91 prepare_directory (path path)
93 using namespace boost::filesystem;
95 create_directories (path);
100 setup (int reference_number, string verify_test_suffix)
102 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
103 prepare_directory (dir);
104 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
105 copy_file (i.path(), dir / i.path().filename());
114 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
116 auto reel = make_shared<dcp::Reel>();
117 reel->add (reel_asset);
118 reel->add (simple_markers());
120 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
122 auto dcp = make_shared<dcp::DCP>(dir);
126 dcp::String::compose("libdcp %1", dcp::version),
127 dcp::String::compose("libdcp %1", dcp::version),
128 dcp::LocalTime().as_string(),
136 /** Class that can alter a file by searching and replacing strings within it.
137 * On destruction modifies the file whose name was given to the constructor.
145 _content = dcp::file_to_string (_path);
150 auto f = fopen(_path.string().c_str(), "w");
152 fwrite (_content.c_str(), _content.length(), 1, f);
156 void replace (string a, string b)
158 auto old_content = _content;
159 boost::algorithm::replace_all (_content, a, b);
160 BOOST_REQUIRE (_content != old_content);
163 void delete_lines (string from, string to)
165 vector<string> lines;
166 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
167 bool deleting = false;
168 auto old_content = _content;
170 for (auto i: lines) {
171 if (i.find(from) != string::npos) {
175 _content += i + "\n";
177 if (deleting && i.find(to) != string::npos) {
181 BOOST_REQUIRE (_content != old_content);
186 std::string _content;
192 dump_notes (vector<dcp::VerificationNote> const & notes)
194 for (auto i: notes) {
195 std::cout << dcp::note_to_string(i) << "\n";
202 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
204 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
206 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
207 for (auto i = 0U; i < notes.size(); ++i) {
208 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
215 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
217 auto dir = setup (1, suffix);
220 Editor e (file(suffix));
221 e.replace (from, to);
224 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
226 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
227 auto i = notes.begin();
228 auto j = codes.begin();
229 while (i != notes.end()) {
230 BOOST_CHECK_EQUAL (i->code(), *j);
237 BOOST_AUTO_TEST_CASE (verify_no_error)
240 auto dir = setup (1, "no_error");
241 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
243 path const cpl_file = dir / dcp_test1_cpl;
244 path const pkl_file = dir / dcp_test1_pkl;
245 path const assetmap_file = dir / "ASSETMAP.xml";
247 auto st = stages.begin();
248 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
249 BOOST_REQUIRE (st->second);
250 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
252 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
253 BOOST_REQUIRE (st->second);
254 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
256 BOOST_CHECK_EQUAL (st->first, "Checking reel");
257 BOOST_REQUIRE (!st->second);
259 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
260 BOOST_REQUIRE (st->second);
261 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
263 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
264 BOOST_REQUIRE (st->second);
265 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
267 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
268 BOOST_REQUIRE (st->second);
269 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
271 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
272 BOOST_REQUIRE (st->second);
273 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
275 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
276 BOOST_REQUIRE (st->second);
277 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
279 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
280 BOOST_REQUIRE (st->second);
281 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
283 BOOST_REQUIRE (st == stages.end());
285 BOOST_CHECK_EQUAL (notes.size(), 0);
289 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
291 using namespace boost::filesystem;
293 auto dir = setup (1, "incorrect_picture_sound_hash");
295 auto video_path = path(dir / "video.mxf");
296 auto mod = fopen(video_path.string().c_str(), "r+b");
298 fseek (mod, 4096, SEEK_SET);
300 fwrite (&x, sizeof(x), 1, mod);
303 auto audio_path = path(dir / "audio.mxf");
304 mod = fopen(audio_path.string().c_str(), "r+b");
306 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
307 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
310 dcp::ASDCPErrorSuspender sus;
311 check_verify_result (
314 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
315 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
320 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
322 using namespace boost::filesystem;
324 auto dir = setup (1, "mismatched_picture_sound_hashes");
327 Editor e (dir / dcp_test1_pkl);
328 e.replace ("<Hash>", "<Hash>x");
331 check_verify_result (
334 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
335 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
336 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xX3bMCBdXEOYEpYmsConNWrWUAGs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
338 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
344 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
346 auto dir = setup (1, "failed_read_content_kind");
349 Editor e (dir / dcp_test1_cpl);
350 e.replace ("<ContentKind>", "<ContentKind>x");
353 check_verify_result (
355 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
364 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
372 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
378 asset_map (string suffix)
380 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
384 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
386 check_verify_result_after_replace (
387 "invalid_picture_frame_rate", &cpl,
388 "<FrameRate>24 1", "<FrameRate>99 1",
389 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
390 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
394 BOOST_AUTO_TEST_CASE (verify_missing_asset)
396 auto dir = setup (1, "missing_asset");
397 remove (dir / "video.mxf");
398 check_verify_result (
401 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
406 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
408 check_verify_result_after_replace (
409 "empty_asset_path", &asset_map,
410 "<Path>video.mxf</Path>", "<Path></Path>",
411 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
416 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
418 check_verify_result_after_replace (
419 "mismatched_standard", &cpl,
420 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
421 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
422 dcp::VerificationNote::Code::INVALID_XML,
423 dcp::VerificationNote::Code::INVALID_XML,
424 dcp::VerificationNote::Code::INVALID_XML,
425 dcp::VerificationNote::Code::INVALID_XML,
426 dcp::VerificationNote::Code::INVALID_XML,
427 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
432 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
434 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
435 check_verify_result_after_replace (
436 "invalid_xml_cpl_id", &cpl,
437 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
438 { dcp::VerificationNote::Code::INVALID_XML }
443 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
445 check_verify_result_after_replace (
446 "invalid_xml_issue_date", &cpl,
447 "<IssueDate>", "<IssueDate>x",
448 { dcp::VerificationNote::Code::INVALID_XML,
449 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
454 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
456 check_verify_result_after_replace (
457 "invalid_xml_pkl_id", &pkl,
458 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
459 { dcp::VerificationNote::Code::INVALID_XML }
464 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
466 check_verify_result_after_replace (
467 "invalix_xml_asset_map_id", &asset_map,
468 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
469 { dcp::VerificationNote::Code::INVALID_XML }
474 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
477 auto dir = setup (3, "verify_invalid_standard");
478 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
480 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
481 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
482 path const assetmap_file = dir / "ASSETMAP";
484 auto st = stages.begin();
485 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
486 BOOST_REQUIRE (st->second);
487 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
489 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
490 BOOST_REQUIRE (st->second);
491 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
493 BOOST_CHECK_EQUAL (st->first, "Checking reel");
494 BOOST_REQUIRE (!st->second);
496 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
497 BOOST_REQUIRE (st->second);
498 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
500 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
501 BOOST_REQUIRE (st->second);
502 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
504 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
505 BOOST_REQUIRE (st->second);
506 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
508 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
509 BOOST_REQUIRE (st->second);
510 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
512 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
513 BOOST_REQUIRE (st->second);
514 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
516 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
517 BOOST_REQUIRE (st->second);
518 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
520 BOOST_REQUIRE (st == stages.end());
522 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
523 auto i = notes.begin ();
524 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
525 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
528 /* DCP with a short asset */
529 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
531 auto dir = setup (8, "invalid_duration");
532 check_verify_result (
535 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
536 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
537 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
538 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
539 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
546 dcp_from_frame (dcp::ArrayData const& frame, path dir)
548 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
549 create_directories (dir);
550 auto writer = asset->start_write (dir / "pic.mxf", true);
551 for (int i = 0; i < 24; ++i) {
552 writer->write (frame.data(), frame.size());
556 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
557 return write_dcp_with_single_asset (dir, reel_asset);
561 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
563 int const too_big = 1302083 * 2;
565 /* Compress a black image */
566 auto image = black_image ();
567 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
568 BOOST_REQUIRE (frame.size() < too_big);
570 /* Place it in a bigger block with some zero padding at the end */
571 dcp::ArrayData oversized_frame(too_big);
572 memcpy (oversized_frame.data(), frame.data(), frame.size());
573 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
575 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
576 prepare_directory (dir);
577 auto cpl = dcp_from_frame (oversized_frame, dir);
579 check_verify_result (
582 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
583 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
588 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
590 int const nearly_too_big = 1302083 * 0.98;
592 /* Compress a black image */
593 auto image = black_image ();
594 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
595 BOOST_REQUIRE (frame.size() < nearly_too_big);
597 /* Place it in a bigger block with some zero padding at the end */
598 dcp::ArrayData oversized_frame(nearly_too_big);
599 memcpy (oversized_frame.data(), frame.data(), frame.size());
600 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
602 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
603 prepare_directory (dir);
604 auto cpl = dcp_from_frame (oversized_frame, dir);
606 check_verify_result (
609 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
610 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
615 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
617 /* Compress a black image */
618 auto image = black_image ();
619 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
620 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
622 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
623 prepare_directory (dir);
624 auto cpl = dcp_from_frame (frame, dir);
626 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
630 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
632 path const dir("build/test/verify_valid_interop_subtitles");
633 prepare_directory (dir);
634 copy_file ("test/data/subs1.xml", dir / "subs.xml");
635 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
636 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
637 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
639 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
643 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
645 using namespace boost::filesystem;
647 path const dir("build/test/verify_invalid_interop_subtitles");
648 prepare_directory (dir);
649 copy_file ("test/data/subs1.xml", dir / "subs.xml");
650 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
651 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
652 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
655 Editor e (dir / "subs.xml");
656 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
659 check_verify_result (
662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
663 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
665 dcp::VerificationNote::Type::ERROR,
666 dcp::VerificationNote::Code::INVALID_XML,
667 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
675 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
677 path const dir("build/test/verify_valid_smpte_subtitles");
678 prepare_directory (dir);
679 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
680 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
681 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(25, 1), 300 * 24, 0);
682 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
684 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
688 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
690 using namespace boost::filesystem;
692 path const dir("build/test/verify_invalid_smpte_subtitles");
693 prepare_directory (dir);
694 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
695 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
696 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
697 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
699 check_verify_result (
702 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
704 dcp::VerificationNote::Type::ERROR,
705 dcp::VerificationNote::Code::INVALID_XML,
706 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
710 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
716 BOOST_AUTO_TEST_CASE (verify_external_asset)
718 path const ov_dir("build/test/verify_external_asset");
719 prepare_directory (ov_dir);
721 auto image = black_image ();
722 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
723 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
724 dcp_from_frame (frame, ov_dir);
726 dcp::DCP ov (ov_dir);
729 path const vf_dir("build/test/verify_external_asset_vf");
730 prepare_directory (vf_dir);
732 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
733 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
735 check_verify_result (
738 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
739 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
744 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
746 path const dir("build/test/verify_valid_cpl_metadata");
747 prepare_directory (dir);
749 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
750 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
751 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
753 auto reel = make_shared<dcp::Reel>();
754 reel->add (reel_asset);
756 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
757 reel->add (simple_markers(16 * 24));
759 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
761 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
762 cpl->set_main_sound_sample_rate (48000);
763 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
764 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
765 cpl->set_version_number (1);
770 dcp::Standard::SMPTE,
771 dcp::String::compose("libdcp %1", dcp::version),
772 dcp::String::compose("libdcp %1", dcp::version),
773 dcp::LocalTime().as_string(),
779 path find_cpl (path dir)
781 for (auto i: directory_iterator(dir)) {
782 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
787 BOOST_REQUIRE (false);
792 /* DCP with invalid CompositionMetadataAsset */
793 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
795 using namespace boost::filesystem;
797 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
798 prepare_directory (dir);
800 auto reel = make_shared<dcp::Reel>();
801 reel->add (black_picture_asset(dir));
802 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
804 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
805 cpl->set_main_sound_sample_rate (48000);
806 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
807 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
808 cpl->set_version_number (1);
810 reel->add (simple_markers());
815 dcp::Standard::SMPTE,
816 dcp::String::compose("libdcp %1", dcp::version),
817 dcp::String::compose("libdcp %1", dcp::version),
818 dcp::LocalTime().as_string(),
823 Editor e (find_cpl(dir));
824 e.replace ("MainSound", "MainSoundX");
827 check_verify_result (
830 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
831 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
833 dcp::VerificationNote::Type::ERROR,
834 dcp::VerificationNote::Code::INVALID_XML,
835 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
836 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
837 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
838 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
839 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
840 "ExtensionMetadataList?,)'"),
841 canonical(cpl->file().get()),
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
849 /* DCP with invalid CompositionMetadataAsset */
850 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
852 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
853 prepare_directory (dir);
855 auto reel = make_shared<dcp::Reel>();
856 reel->add (black_picture_asset(dir));
857 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
859 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
860 cpl->set_main_sound_sample_rate (48000);
861 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
862 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
867 dcp::Standard::SMPTE,
868 dcp::String::compose("libdcp %1", dcp::version),
869 dcp::String::compose("libdcp %1", dcp::version),
870 dcp::LocalTime().as_string(),
875 Editor e (find_cpl(dir));
876 e.replace ("meta:Width", "meta:WidthX");
879 check_verify_result (
881 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
886 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
888 path const dir("build/test/verify_invalid_language1");
889 prepare_directory (dir);
890 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
891 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
892 asset->_language = "wrong-andbad";
893 asset->write (dir / "subs.mxf");
894 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
895 reel_asset->_language = "badlang";
896 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
898 check_verify_result (
901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
903 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
908 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
909 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
911 path const dir("build/test/verify_invalid_language2");
912 prepare_directory (dir);
913 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
914 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
915 asset->_language = "wrong-andbad";
916 asset->write (dir / "subs.mxf");
917 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
918 reel_asset->_language = "badlang";
919 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
921 check_verify_result (
924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
925 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
926 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
931 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
932 * the release territory.
934 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
936 path const dir("build/test/verify_invalid_language3");
937 prepare_directory (dir);
939 auto picture = simple_picture (dir, "foo");
940 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
941 auto reel = make_shared<dcp::Reel>();
942 reel->add (reel_picture);
943 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
944 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
945 reel->add (reel_sound);
946 reel->add (simple_markers());
948 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
950 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
951 cpl->_additional_subtitle_languages.push_back("andso-is-this");
952 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
953 cpl->set_main_sound_sample_rate (48000);
954 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
955 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
956 cpl->set_version_number (1);
957 cpl->_release_territory = "fred-jim";
958 auto dcp = make_shared<dcp::DCP>(dir);
961 dcp::Standard::SMPTE,
962 dcp::String::compose("libdcp %1", dcp::version),
963 dcp::String::compose("libdcp %1", dcp::version),
964 dcp::LocalTime().as_string(),
968 check_verify_result (
971 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
972 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
973 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
974 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
980 vector<dcp::VerificationNote>
981 check_picture_size (int width, int height, int frame_rate, bool three_d)
983 using namespace boost::filesystem;
985 path dcp_path = "build/test/verify_picture_test";
986 prepare_directory (dcp_path);
988 shared_ptr<dcp::PictureAsset> mp;
990 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
992 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
994 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
996 auto image = black_image (dcp::Size(width, height));
997 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
998 int const length = three_d ? frame_rate * 2 : frame_rate;
999 for (int i = 0; i < length; ++i) {
1000 picture_writer->write (j2c.data(), j2c.size());
1002 picture_writer->finalize ();
1004 auto d = make_shared<dcp::DCP>(dcp_path);
1005 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1006 cpl->set_annotation_text ("A Test DCP");
1007 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1008 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1009 cpl->set_main_sound_sample_rate (48000);
1010 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1011 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1012 cpl->set_version_number (1);
1014 auto reel = make_shared<dcp::Reel>();
1017 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1019 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1022 reel->add (simple_markers(frame_rate));
1028 dcp::Standard::SMPTE,
1029 dcp::String::compose("libdcp %1", dcp::version),
1030 dcp::String::compose("libdcp %1", dcp::version),
1031 dcp::LocalTime().as_string(),
1035 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1041 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1043 auto notes = check_picture_size(width, height, frame_rate, three_d);
1045 BOOST_CHECK_EQUAL (notes.size(), 0U);
1051 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1053 auto notes = check_picture_size(width, height, frame_rate, three_d);
1054 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1055 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1056 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1062 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1064 auto notes = check_picture_size(width, height, frame_rate, three_d);
1065 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1066 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1067 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1073 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1075 auto notes = check_picture_size(width, height, frame_rate, three_d);
1076 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1077 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1078 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1082 BOOST_AUTO_TEST_CASE (verify_picture_size)
1084 using namespace boost::filesystem;
1087 check_picture_size_ok (2048, 858, 24, false);
1088 check_picture_size_ok (2048, 858, 25, false);
1089 check_picture_size_ok (2048, 858, 48, false);
1090 check_picture_size_ok (2048, 858, 24, true);
1091 check_picture_size_ok (2048, 858, 25, true);
1092 check_picture_size_ok (2048, 858, 48, true);
1095 check_picture_size_ok (1998, 1080, 24, false);
1096 check_picture_size_ok (1998, 1080, 25, false);
1097 check_picture_size_ok (1998, 1080, 48, false);
1098 check_picture_size_ok (1998, 1080, 24, true);
1099 check_picture_size_ok (1998, 1080, 25, true);
1100 check_picture_size_ok (1998, 1080, 48, true);
1103 check_picture_size_ok (4096, 1716, 24, false);
1106 check_picture_size_ok (3996, 2160, 24, false);
1108 /* Bad frame size */
1109 check_picture_size_bad_frame_size (2050, 858, 24, false);
1110 check_picture_size_bad_frame_size (2048, 658, 25, false);
1111 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1112 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1114 /* Bad 2K frame rate */
1115 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1116 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1117 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1119 /* Bad 4K frame rate */
1120 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1121 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1124 auto notes = check_picture_size(3996, 2160, 24, true);
1125 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1126 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1127 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1133 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1136 make_shared<dcp::SubtitleString>(
1144 dcp::Time(start_frame, 24, 24),
1145 dcp::Time(end_frame, 24, 24),
1147 dcp::HAlign::CENTER,
1149 dcp::VAlign::CENTER,
1150 dcp::Direction::LTR,
1161 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1163 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1164 prepare_directory (dir);
1166 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1167 for (int i = 0; i < 2048; ++i) {
1168 add_test_subtitle (asset, i * 24, i * 24 + 20);
1170 asset->set_language (dcp::LanguageTag("de-DE"));
1171 asset->write (dir / "subs.mxf");
1172 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 2049 * 24, 0);
1173 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1175 check_verify_result (
1178 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1180 dcp::VerificationNote::Type::BV21_ERROR,
1181 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1183 canonical(dir / "subs.mxf")
1185 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1186 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1192 shared_ptr<dcp::SMPTESubtitleAsset>
1193 make_large_subtitle_asset (path font_file)
1195 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1196 dcp::ArrayData big_fake_font(1024 * 1024);
1197 big_fake_font.write (font_file);
1198 for (int i = 0; i < 116; ++i) {
1199 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1207 verify_timed_text_asset_too_large (string name)
1209 auto const dir = path("build/test") / name;
1210 prepare_directory (dir);
1211 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1212 add_test_subtitle (asset, 0, 20);
1213 asset->set_language (dcp::LanguageTag("de-DE"));
1214 asset->write (dir / "subs.mxf");
1216 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1217 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1219 check_verify_result (
1222 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1223 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1224 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1225 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1226 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1231 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1233 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1234 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1238 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1240 path dir = "build/test/verify_missing_subtitle_language";
1241 prepare_directory (dir);
1242 auto dcp = make_simple (dir, 1, 240);
1245 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1246 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1247 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1248 "<ContentTitleText>Content</ContentTitleText>"
1249 "<AnnotationText>Annotation</AnnotationText>"
1250 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1251 "<ReelNumber>1</ReelNumber>"
1252 "<EditRate>25 1</EditRate>"
1253 "<TimeCodeRate>25</TimeCodeRate>"
1254 "<StartTime>00:00:00:00</StartTime>"
1255 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1257 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1258 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1259 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1265 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1266 BOOST_REQUIRE (xml_file);
1267 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1269 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1270 subs->write (dir / "subs.mxf");
1272 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1273 dcp->cpls().front()->reels().front()->add(reel_subs);
1275 dcp::Standard::SMPTE,
1276 dcp::String::compose("libdcp %1", dcp::version),
1277 dcp::String::compose("libdcp %1", dcp::version),
1278 dcp::LocalTime().as_string(),
1282 check_verify_result (
1285 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1286 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1291 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1293 path path ("build/test/verify_mismatched_subtitle_languages");
1294 auto dcp = make_simple (path, 2, 240);
1295 auto cpl = dcp->cpls()[0];
1298 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1299 subs->set_language (dcp::LanguageTag("de-DE"));
1300 subs->add (simple_subtitle());
1301 subs->write (path / "subs1.mxf");
1302 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1303 cpl->reels()[0]->add(reel_subs);
1307 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1308 subs->set_language (dcp::LanguageTag("en-US"));
1309 subs->add (simple_subtitle());
1310 subs->write (path / "subs2.mxf");
1311 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1312 cpl->reels()[1]->add(reel_subs);
1316 dcp::Standard::SMPTE,
1317 dcp::String::compose("libdcp %1", dcp::version),
1318 dcp::String::compose("libdcp %1", dcp::version),
1319 dcp::LocalTime().as_string(),
1323 check_verify_result (
1326 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1333 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1335 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1336 auto dcp = make_simple (path, 2, 240);
1337 auto cpl = dcp->cpls()[0];
1340 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1341 ccaps->set_language (dcp::LanguageTag("de-DE"));
1342 ccaps->add (simple_subtitle());
1343 ccaps->write (path / "subs1.mxf");
1344 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1345 cpl->reels()[0]->add(reel_ccaps);
1349 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1350 ccaps->set_language (dcp::LanguageTag("en-US"));
1351 ccaps->add (simple_subtitle());
1352 ccaps->write (path / "subs2.mxf");
1353 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1354 cpl->reels()[1]->add(reel_ccaps);
1358 dcp::Standard::SMPTE,
1359 dcp::String::compose("libdcp %1", dcp::version),
1360 dcp::String::compose("libdcp %1", dcp::version),
1361 dcp::LocalTime().as_string(),
1365 check_verify_result (
1368 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1369 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1374 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1376 path dir = "build/test/verify_missing_subtitle_start_time";
1377 prepare_directory (dir);
1378 auto dcp = make_simple (dir, 1, 240);
1381 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1382 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1383 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1384 "<ContentTitleText>Content</ContentTitleText>"
1385 "<AnnotationText>Annotation</AnnotationText>"
1386 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1387 "<ReelNumber>1</ReelNumber>"
1388 "<Language>de-DE</Language>"
1389 "<EditRate>25 1</EditRate>"
1390 "<TimeCodeRate>25</TimeCodeRate>"
1391 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1393 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1394 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1395 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1401 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1402 BOOST_REQUIRE (xml_file);
1403 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1405 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1406 subs->write (dir / "subs.mxf");
1408 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1409 dcp->cpls().front()->reels().front()->add(reel_subs);
1411 dcp::Standard::SMPTE,
1412 dcp::String::compose("libdcp %1", dcp::version),
1413 dcp::String::compose("libdcp %1", dcp::version),
1414 dcp::LocalTime().as_string(),
1418 check_verify_result (
1421 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1422 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1427 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1429 path dir = "build/test/verify_invalid_subtitle_start_time";
1430 prepare_directory (dir);
1431 auto dcp = make_simple (dir, 1, 240);
1434 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1435 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1436 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1437 "<ContentTitleText>Content</ContentTitleText>"
1438 "<AnnotationText>Annotation</AnnotationText>"
1439 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1440 "<ReelNumber>1</ReelNumber>"
1441 "<Language>de-DE</Language>"
1442 "<EditRate>25 1</EditRate>"
1443 "<TimeCodeRate>25</TimeCodeRate>"
1444 "<StartTime>00:00:02:00</StartTime>"
1445 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1447 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1448 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1449 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1455 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1456 BOOST_REQUIRE (xml_file);
1457 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1459 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1460 subs->write (dir / "subs.mxf");
1462 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1463 dcp->cpls().front()->reels().front()->add(reel_subs);
1465 dcp::Standard::SMPTE,
1466 dcp::String::compose("libdcp %1", dcp::version),
1467 dcp::String::compose("libdcp %1", dcp::version),
1468 dcp::LocalTime().as_string(),
1472 check_verify_result (
1475 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1476 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1484 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1487 , v_position(v_position_)
1499 shared_ptr<dcp::CPL>
1500 dcp_with_text (path dir, vector<TestText> subs)
1502 prepare_directory (dir);
1503 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1504 asset->set_start_time (dcp::Time());
1505 for (auto i: subs) {
1506 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1508 asset->set_language (dcp::LanguageTag("de-DE"));
1509 asset->write (dir / "subs.mxf");
1511 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1512 return write_dcp_with_single_asset (dir, reel_asset);
1516 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1518 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1519 /* Just too early */
1520 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1521 check_verify_result (
1524 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1525 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1531 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1533 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1534 /* Just late enough */
1535 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1536 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1540 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1542 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1543 prepare_directory (dir);
1545 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1546 asset1->set_start_time (dcp::Time());
1547 /* Just late enough */
1548 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1549 asset1->set_language (dcp::LanguageTag("de-DE"));
1550 asset1->write (dir / "subs1.mxf");
1551 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1552 auto reel1 = make_shared<dcp::Reel>();
1553 reel1->add (reel_asset1);
1554 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1555 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1556 reel1->add (markers1);
1558 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1559 asset2->set_start_time (dcp::Time());
1560 /* This would be too early on first reel but should be OK on the second */
1561 add_test_subtitle (asset2, 0, 4 * 24);
1562 asset2->set_language (dcp::LanguageTag("de-DE"));
1563 asset2->write (dir / "subs2.mxf");
1564 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1565 auto reel2 = make_shared<dcp::Reel>();
1566 reel2->add (reel_asset2);
1567 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1568 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1569 reel2->add (markers2);
1571 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1574 auto dcp = make_shared<dcp::DCP>(dir);
1577 dcp::Standard::SMPTE,
1578 dcp::String::compose("libdcp %1", dcp::version),
1579 dcp::String::compose("libdcp %1", dcp::version),
1580 dcp::LocalTime().as_string(),
1585 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1589 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1591 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1592 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1596 { 5 * 24 + 1, 6 * 24 },
1598 check_verify_result (
1601 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1602 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1607 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1609 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1610 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1614 { 5 * 24 + 16, 8 * 24 },
1616 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1620 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1622 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1623 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1624 check_verify_result (
1627 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1628 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1633 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1635 auto const dir = path("build/test/verify_valid_subtitle_duration");
1636 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1637 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1641 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1643 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1644 prepare_directory (dir);
1645 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1646 asset->set_start_time (dcp::Time());
1647 add_test_subtitle (asset, 0, 4 * 24);
1648 asset->set_language (dcp::LanguageTag("de-DE"));
1649 asset->write (dir / "subs.mxf");
1651 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1652 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1653 check_verify_result (
1656 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1657 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1658 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1664 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1666 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1667 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1670 { 96, 200, 0.0, "We" },
1671 { 96, 200, 0.1, "have" },
1672 { 96, 200, 0.2, "four" },
1673 { 96, 200, 0.3, "lines" }
1675 check_verify_result (
1678 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1679 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1684 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1686 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1687 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1690 { 96, 200, 0.0, "We" },
1691 { 96, 200, 0.1, "have" },
1692 { 96, 200, 0.2, "four" },
1694 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1698 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1700 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1701 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1704 { 96, 300, 0.0, "We" },
1705 { 96, 300, 0.1, "have" },
1706 { 150, 180, 0.2, "four" },
1707 { 150, 180, 0.3, "lines" }
1709 check_verify_result (
1712 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1713 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1718 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1720 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1721 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1724 { 96, 300, 0.0, "We" },
1725 { 96, 300, 0.1, "have" },
1726 { 150, 180, 0.2, "four" },
1727 { 190, 250, 0.3, "lines" }
1729 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1733 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1735 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1736 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1739 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1741 check_verify_result (
1744 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1745 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1750 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1752 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1753 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1756 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1758 check_verify_result (
1761 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1762 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1767 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1769 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1770 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1773 { 96, 200, 0.0, "We" },
1774 { 96, 200, 0.1, "have" },
1775 { 96, 200, 0.2, "four" },
1776 { 96, 200, 0.3, "lines" }
1778 check_verify_result (
1781 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1782 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1787 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1789 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1790 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1793 { 96, 200, 0.0, "We" },
1794 { 96, 200, 0.1, "have" },
1795 { 96, 200, 0.2, "four" },
1797 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1801 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1803 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1804 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1807 { 96, 300, 0.0, "We" },
1808 { 96, 300, 0.1, "have" },
1809 { 150, 180, 0.2, "four" },
1810 { 150, 180, 0.3, "lines" }
1812 check_verify_result (
1815 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1816 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1821 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1823 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1824 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1827 { 96, 300, 0.0, "We" },
1828 { 96, 300, 0.1, "have" },
1829 { 150, 180, 0.2, "four" },
1830 { 190, 250, 0.3, "lines" }
1832 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1836 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1838 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1839 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1842 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1844 check_verify_result (
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1848 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1853 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1855 path const dir("build/test/verify_invalid_sound_frame_rate");
1856 prepare_directory (dir);
1858 auto picture = simple_picture (dir, "foo");
1859 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1860 auto reel = make_shared<dcp::Reel>();
1861 reel->add (reel_picture);
1862 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1863 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1864 reel->add (reel_sound);
1865 reel->add (simple_markers());
1866 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1868 auto dcp = make_shared<dcp::DCP>(dir);
1871 dcp::Standard::SMPTE,
1872 dcp::String::compose("libdcp %1", dcp::version),
1873 dcp::String::compose("libdcp %1", dcp::version),
1874 dcp::LocalTime().as_string(),
1878 check_verify_result (
1881 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1882 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1887 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1889 path const dir("build/test/verify_missing_cpl_annotation_text");
1890 auto dcp = make_simple (dir);
1892 dcp::Standard::SMPTE,
1893 dcp::String::compose("libdcp %1", dcp::version),
1894 dcp::String::compose("libdcp %1", dcp::version),
1895 dcp::LocalTime().as_string(),
1899 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1901 auto const cpl = dcp->cpls()[0];
1904 BOOST_REQUIRE (cpl->file());
1905 Editor e(cpl->file().get());
1906 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1909 check_verify_result (
1912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1913 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1918 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1920 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1921 auto dcp = make_simple (dir);
1923 dcp::Standard::SMPTE,
1924 dcp::String::compose("libdcp %1", dcp::version),
1925 dcp::String::compose("libdcp %1", dcp::version),
1926 dcp::LocalTime().as_string(),
1930 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1931 auto const cpl = dcp->cpls()[0];
1934 BOOST_REQUIRE (cpl->file());
1935 Editor e(cpl->file().get());
1936 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1939 check_verify_result (
1942 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1943 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1948 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1950 path const dir("build/test/verify_mismatched_asset_duration");
1951 prepare_directory (dir);
1952 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1953 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1955 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1956 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1958 auto reel = make_shared<dcp::Reel>(
1959 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1960 make_shared<dcp::ReelSoundAsset>(ms, 0)
1963 reel->add (simple_markers());
1968 dcp::Standard::SMPTE,
1969 dcp::String::compose("libdcp %1", dcp::version),
1970 dcp::String::compose("libdcp %1", dcp::version),
1971 dcp::LocalTime().as_string(),
1975 check_verify_result (
1978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1986 shared_ptr<dcp::CPL>
1987 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1989 prepare_directory (dir);
1990 auto dcp = make_shared<dcp::DCP>(dir);
1991 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1993 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1994 subs->set_language (dcp::LanguageTag("de-DE"));
1995 subs->set_start_time (dcp::Time());
1996 subs->add (simple_subtitle());
1997 subs->write (dir / "subs.mxf");
1998 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
2000 auto reel1 = make_shared<dcp::Reel>(
2001 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2002 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2006 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2009 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2010 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2011 reel1->add (markers1);
2015 auto reel2 = make_shared<dcp::Reel>(
2016 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2017 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2021 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2024 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2025 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2026 reel2->add (markers2);
2032 dcp::Standard::SMPTE,
2033 dcp::String::compose("libdcp %1", dcp::version),
2034 dcp::String::compose("libdcp %1", dcp::version),
2035 dcp::LocalTime().as_string(),
2043 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2046 path dir ("build/test/missing_main_subtitle_from_some_reels");
2047 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2048 check_verify_result (
2051 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2052 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2058 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2059 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2060 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2064 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2065 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2066 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2072 shared_ptr<dcp::CPL>
2073 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2075 prepare_directory (dir);
2076 auto dcp = make_shared<dcp::DCP>(dir);
2077 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2079 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2080 subs->set_language (dcp::LanguageTag("de-DE"));
2081 subs->set_start_time (dcp::Time());
2082 subs->add (simple_subtitle());
2083 subs->write (dir / "subs.mxf");
2085 auto reel1 = make_shared<dcp::Reel>(
2086 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2087 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2090 for (int i = 0; i < caps_in_reel1; ++i) {
2091 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2094 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2095 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2096 reel1->add (markers1);
2100 auto reel2 = make_shared<dcp::Reel>(
2101 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2102 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2105 for (int i = 0; i < caps_in_reel2; ++i) {
2106 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2109 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2110 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2111 reel2->add (markers2);
2117 dcp::Standard::SMPTE,
2118 dcp::String::compose("libdcp %1", dcp::version),
2119 dcp::String::compose("libdcp %1", dcp::version),
2120 dcp::LocalTime().as_string(),
2128 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2131 path dir ("build/test/mismatched_closed_caption_asset_counts");
2132 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2133 check_verify_result (
2136 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2137 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2142 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2143 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2144 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2148 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2149 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2150 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2157 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2159 prepare_directory (dir);
2160 auto dcp = make_shared<dcp::DCP>(dir);
2161 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2163 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2164 subs->set_language (dcp::LanguageTag("de-DE"));
2165 subs->set_start_time (dcp::Time());
2166 subs->add (simple_subtitle());
2167 subs->write (dir / "subs.mxf");
2168 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2171 auto reel = make_shared<dcp::Reel>(
2172 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2173 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2176 reel->add (reel_text);
2178 reel->add (simple_markers(240));
2184 dcp::Standard::SMPTE,
2185 dcp::String::compose("libdcp %1", dcp::version),
2186 dcp::String::compose("libdcp %1", dcp::version),
2187 dcp::LocalTime().as_string(),
2191 check_verify_result (
2194 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2195 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2200 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2202 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2203 "build/test/verify_subtitle_entry_point_must_be_present",
2204 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2205 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2206 asset->unset_entry_point ();
2210 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2211 "build/test/verify_subtitle_entry_point_must_be_zero",
2212 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2213 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2214 asset->set_entry_point (4);
2218 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2219 "build/test/verify_closed_caption_entry_point_must_be_present",
2220 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2221 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2222 asset->unset_entry_point ();
2226 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2227 "build/test/verify_closed_caption_entry_point_must_be_zero",
2228 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2229 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2230 asset->set_entry_point (9);
2236 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2240 path const dir("build/test/verify_missing_hash");
2241 auto dcp = make_simple (dir);
2243 dcp::Standard::SMPTE,
2244 dcp::String::compose("libdcp %1", dcp::version),
2245 dcp::String::compose("libdcp %1", dcp::version),
2246 dcp::LocalTime().as_string(),
2250 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2251 auto const cpl = dcp->cpls()[0];
2254 BOOST_REQUIRE (cpl->file());
2255 Editor e(cpl->file().get());
2256 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2259 check_verify_result (
2262 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2263 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2270 verify_markers_test (
2272 vector<pair<dcp::Marker, dcp::Time>> markers,
2273 vector<dcp::VerificationNote> test_notes
2276 auto dcp = make_simple (dir);
2277 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2278 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2279 for (auto const& i: markers) {
2280 markers_asset->set (i.first, i.second);
2282 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2284 dcp::Standard::SMPTE,
2285 dcp::String::compose("libdcp %1", dcp::version),
2286 dcp::String::compose("libdcp %1", dcp::version),
2287 dcp::LocalTime().as_string(),
2291 check_verify_result ({dir}, test_notes);
2295 BOOST_AUTO_TEST_CASE (verify_markers)
2297 verify_markers_test (
2298 "build/test/verify_markers_all_correct",
2300 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2301 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2302 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2303 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2308 verify_markers_test (
2309 "build/test/verify_markers_missing_ffec",
2311 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2312 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2313 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2316 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2319 verify_markers_test (
2320 "build/test/verify_markers_missing_ffmc",
2322 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2323 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2324 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2330 verify_markers_test (
2331 "build/test/verify_markers_missing_ffoc",
2333 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2334 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2335 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2338 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2341 verify_markers_test (
2342 "build/test/verify_markers_missing_lfoc",
2344 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2345 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2346 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2349 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2352 verify_markers_test (
2353 "build/test/verify_markers_incorrect_ffoc",
2355 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2356 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2357 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2358 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2361 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2364 verify_markers_test (
2365 "build/test/verify_markers_incorrect_lfoc",
2367 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2368 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2369 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2370 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2373 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2378 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2380 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2381 prepare_directory (dir);
2382 auto dcp = make_simple (dir);
2383 auto cpl = dcp->cpls()[0];
2384 cpl->unset_version_number();
2386 dcp::Standard::SMPTE,
2387 dcp::String::compose("libdcp %1", dcp::version),
2388 dcp::String::compose("libdcp %1", dcp::version),
2389 dcp::LocalTime().as_string(),
2393 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2397 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2399 path dir = "build/test/verify_missing_extension_metadata1";
2400 auto dcp = make_simple (dir);
2402 dcp::Standard::SMPTE,
2403 dcp::String::compose("libdcp %1", dcp::version),
2404 dcp::String::compose("libdcp %1", dcp::version),
2405 dcp::LocalTime().as_string(),
2409 auto cpl = dcp->cpls()[0];
2412 Editor e (cpl->file().get());
2413 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2416 check_verify_result (
2419 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2420 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2425 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2427 path dir = "build/test/verify_missing_extension_metadata2";
2428 auto dcp = make_simple (dir);
2430 dcp::Standard::SMPTE,
2431 dcp::String::compose("libdcp %1", dcp::version),
2432 dcp::String::compose("libdcp %1", dcp::version),
2433 dcp::LocalTime().as_string(),
2437 auto cpl = dcp->cpls()[0];
2440 Editor e (cpl->file().get());
2441 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2444 check_verify_result (
2447 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2448 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2453 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2455 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2456 auto dcp = make_simple (dir);
2458 dcp::Standard::SMPTE,
2459 dcp::String::compose("libdcp %1", dcp::version),
2460 dcp::String::compose("libdcp %1", dcp::version),
2461 dcp::LocalTime().as_string(),
2465 auto const cpl = dcp->cpls()[0];
2468 Editor e (cpl->file().get());
2469 e.replace ("<meta:Name>A", "<meta:NameX>A");
2470 e.replace ("n</meta:Name>", "n</meta:NameX>");
2473 check_verify_result (
2476 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2477 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2478 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2483 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2485 path dir = "build/test/verify_invalid_extension_metadata1";
2486 auto dcp = make_simple (dir);
2488 dcp::Standard::SMPTE,
2489 dcp::String::compose("libdcp %1", dcp::version),
2490 dcp::String::compose("libdcp %1", dcp::version),
2491 dcp::LocalTime().as_string(),
2495 auto cpl = dcp->cpls()[0];
2498 Editor e (cpl->file().get());
2499 e.replace ("Application", "Fred");
2502 check_verify_result (
2505 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2506 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2511 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2513 path dir = "build/test/verify_invalid_extension_metadata2";
2514 auto dcp = make_simple (dir);
2516 dcp::Standard::SMPTE,
2517 dcp::String::compose("libdcp %1", dcp::version),
2518 dcp::String::compose("libdcp %1", dcp::version),
2519 dcp::LocalTime().as_string(),
2523 auto cpl = dcp->cpls()[0];
2526 Editor e (cpl->file().get());
2527 e.replace ("DCP Constraints Profile", "Fred");
2530 check_verify_result (
2533 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2539 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2541 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2542 auto dcp = make_simple (dir);
2544 dcp::Standard::SMPTE,
2545 dcp::String::compose("libdcp %1", dcp::version),
2546 dcp::String::compose("libdcp %1", dcp::version),
2547 dcp::LocalTime().as_string(),
2551 auto const cpl = dcp->cpls()[0];
2554 Editor e (cpl->file().get());
2555 e.replace ("<meta:Value>", "<meta:ValueX>");
2556 e.replace ("</meta:Value>", "</meta:ValueX>");
2559 check_verify_result (
2562 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2563 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 },
2564 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2569 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2571 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2572 auto dcp = make_simple (dir);
2574 dcp::Standard::SMPTE,
2575 dcp::String::compose("libdcp %1", dcp::version),
2576 dcp::String::compose("libdcp %1", dcp::version),
2577 dcp::LocalTime().as_string(),
2581 auto const cpl = dcp->cpls()[0];
2584 Editor e (cpl->file().get());
2585 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2588 check_verify_result (
2591 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2592 { 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() },
2597 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2599 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2600 auto dcp = make_simple (dir);
2602 dcp::Standard::SMPTE,
2603 dcp::String::compose("libdcp %1", dcp::version),
2604 dcp::String::compose("libdcp %1", dcp::version),
2605 dcp::LocalTime().as_string(),
2609 auto const cpl = dcp->cpls()[0];
2612 Editor e (cpl->file().get());
2613 e.replace ("<meta:Property>", "<meta:PropertyX>");
2614 e.replace ("</meta:Property>", "</meta:PropertyX>");
2617 check_verify_result (
2620 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2621 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2622 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2627 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2629 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2630 auto dcp = make_simple (dir);
2632 dcp::Standard::SMPTE,
2633 dcp::String::compose("libdcp %1", dcp::version),
2634 dcp::String::compose("libdcp %1", dcp::version),
2635 dcp::LocalTime().as_string(),
2639 auto const cpl = dcp->cpls()[0];
2642 Editor e (cpl->file().get());
2643 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2644 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2647 check_verify_result (
2650 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2651 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2652 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2658 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2660 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2661 prepare_directory (dir);
2662 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2663 copy_file (i.path(), dir / i.path().filename());
2666 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2667 path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2668 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2669 path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2673 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2676 check_verify_result (
2679 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2680 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2681 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2682 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2683 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2684 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2686 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2691 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2693 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2694 prepare_directory (dir);
2695 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2696 copy_file (i.path(), dir / i.path().filename());
2699 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2700 path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2701 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2702 path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2705 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2708 check_verify_result (
2711 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2712 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2713 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2714 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2715 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2722 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2724 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2725 prepare_directory (dir);
2726 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2727 copy_file (i.path(), dir / i.path().filename());
2731 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2732 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2735 check_verify_result ({dir}, {});
2739 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2741 path dir ("build/test/verify_must_not_be_partially_encrypted");
2742 prepare_directory (dir);
2746 auto signer = make_shared<dcp::CertificateChain>();
2747 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2748 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2749 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2750 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2752 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2756 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2759 auto writer = mp->start_write (dir / "video.mxf", false);
2760 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2761 for (int i = 0; i < 24; ++i) {
2762 writer->write (j2c.data(), j2c.size());
2764 writer->finalize ();
2766 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2768 auto reel = make_shared<dcp::Reel>(
2769 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2770 make_shared<dcp::ReelSoundAsset>(ms, 0)
2773 reel->add (simple_markers());
2777 cpl->set_content_version (
2778 {"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"}
2780 cpl->set_annotation_text ("A Test DCP");
2781 cpl->set_issuer ("OpenDCP 0.0.25");
2782 cpl->set_creator ("OpenDCP 0.0.25");
2783 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2784 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2785 cpl->set_main_sound_sample_rate (48000);
2786 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2787 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2788 cpl->set_version_number (1);
2792 d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2794 check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});
2798 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2800 vector<dcp::VerificationNote> notes;
2801 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"));
2802 auto reader = picture.start_read ();
2803 auto frame = reader->get_frame (0);
2804 verify_j2k (frame, notes);
2809 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2811 vector<dcp::VerificationNote> notes;
2812 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2813 auto reader = picture.start_read ();
2814 auto frame = reader->get_frame (0);
2815 verify_j2k (frame, notes);
2820 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2822 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2823 prepare_directory (dir);
2824 auto dcp = make_simple (dir);
2825 dcp->write_xml (dcp::Standard::SMPTE);
2826 vector<dcp::VerificationNote> notes;
2827 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2828 auto reader = picture.start_read ();
2829 auto frame = reader->get_frame (0);
2830 verify_j2k (frame, notes);