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_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
75 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
76 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
77 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
78 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
79 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
80 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
83 stage (string s, optional<path> p)
85 stages.push_back (make_pair (s, p));
95 prepare_directory (path path)
97 using namespace boost::filesystem;
99 create_directories (path);
104 setup (int reference_number, string verify_test_suffix)
106 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
107 prepare_directory (dir);
108 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
109 copy_file (i.path(), dir / i.path().filename());
118 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
120 auto reel = make_shared<dcp::Reel>();
121 reel->add (reel_asset);
122 reel->add (simple_markers());
124 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
126 auto dcp = make_shared<dcp::DCP>(dir);
130 dcp::String::compose("libdcp %1", dcp::version),
131 dcp::String::compose("libdcp %1", dcp::version),
132 dcp::LocalTime().as_string(),
140 /** Class that can alter a file by searching and replacing strings within it.
141 * On destruction modifies the file whose name was given to the constructor.
149 _content = dcp::file_to_string (_path);
154 auto f = fopen(_path.string().c_str(), "w");
156 fwrite (_content.c_str(), _content.length(), 1, f);
160 void replace (string a, string b)
162 auto old_content = _content;
163 boost::algorithm::replace_all (_content, a, b);
164 BOOST_REQUIRE (_content != old_content);
167 void delete_lines (string from, string to)
169 vector<string> lines;
170 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
171 bool deleting = false;
172 auto old_content = _content;
174 for (auto i: lines) {
175 if (i.find(from) != string::npos) {
179 _content += i + "\n";
181 if (deleting && i.find(to) != string::npos) {
185 BOOST_REQUIRE (_content != old_content);
190 std::string _content;
196 dump_notes (vector<dcp::VerificationNote> const & notes)
198 for (auto i: notes) {
199 std::cout << dcp::note_to_string(i) << "\n";
206 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
208 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
210 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
211 for (auto i = 0U; i < notes.size(); ++i) {
212 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
219 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
221 auto dir = setup (1, suffix);
224 Editor e (file(suffix));
225 e.replace (from, to);
228 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
230 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
231 auto i = notes.begin();
232 auto j = codes.begin();
233 while (i != notes.end()) {
234 BOOST_CHECK_EQUAL (i->code(), *j);
241 BOOST_AUTO_TEST_CASE (verify_no_error)
244 auto dir = setup (1, "no_error");
245 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
247 path const cpl_file = dir / dcp_test1_cpl;
248 path const pkl_file = dir / dcp_test1_pkl;
249 path const assetmap_file = dir / "ASSETMAP.xml";
251 auto st = stages.begin();
252 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
253 BOOST_REQUIRE (st->second);
254 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
256 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
257 BOOST_REQUIRE (st->second);
258 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
260 BOOST_CHECK_EQUAL (st->first, "Checking reel");
261 BOOST_REQUIRE (!st->second);
263 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
264 BOOST_REQUIRE (st->second);
265 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
267 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
268 BOOST_REQUIRE (st->second);
269 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
271 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
272 BOOST_REQUIRE (st->second);
273 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
275 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
276 BOOST_REQUIRE (st->second);
277 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
279 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
280 BOOST_REQUIRE (st->second);
281 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
283 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
284 BOOST_REQUIRE (st->second);
285 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
287 BOOST_REQUIRE (st == stages.end());
289 BOOST_CHECK_EQUAL (notes.size(), 0);
293 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
295 using namespace boost::filesystem;
297 auto dir = setup (1, "incorrect_picture_sound_hash");
299 auto video_path = path(dir / "video.mxf");
300 auto mod = fopen(video_path.string().c_str(), "r+b");
302 fseek (mod, 4096, SEEK_SET);
304 fwrite (&x, sizeof(x), 1, mod);
307 auto audio_path = path(dir / "audio.mxf");
308 mod = fopen(audio_path.string().c_str(), "r+b");
310 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
311 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
314 dcp::ASDCPErrorSuspender sus;
315 check_verify_result (
318 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
324 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
326 using namespace boost::filesystem;
328 auto dir = setup (1, "mismatched_picture_sound_hashes");
331 Editor e (dir / dcp_test1_pkl);
332 e.replace ("<Hash>", "<Hash>x");
335 check_verify_result (
338 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xWU0/u1wM17y7Kriq06+65/ViX1o=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
348 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
350 auto dir = setup (1, "failed_read_content_kind");
353 Editor e (dir / dcp_test1_cpl);
354 e.replace ("<ContentKind>", "<ContentKind>x");
357 check_verify_result (
359 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
368 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
376 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
382 asset_map (string suffix)
384 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
388 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
390 check_verify_result_after_replace (
391 "invalid_picture_frame_rate", &cpl,
392 "<FrameRate>24 1", "<FrameRate>99 1",
393 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
394 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
398 BOOST_AUTO_TEST_CASE (verify_missing_asset)
400 auto dir = setup (1, "missing_asset");
401 remove (dir / "video.mxf");
402 check_verify_result (
405 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
410 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
412 check_verify_result_after_replace (
413 "empty_asset_path", &asset_map,
414 "<Path>video.mxf</Path>", "<Path></Path>",
415 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
420 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
422 check_verify_result_after_replace (
423 "mismatched_standard", &cpl,
424 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
425 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
426 dcp::VerificationNote::Code::INVALID_XML,
427 dcp::VerificationNote::Code::INVALID_XML,
428 dcp::VerificationNote::Code::INVALID_XML,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
436 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
438 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
439 check_verify_result_after_replace (
440 "invalid_xml_cpl_id", &cpl,
441 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
442 { dcp::VerificationNote::Code::INVALID_XML }
447 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
449 check_verify_result_after_replace (
450 "invalid_xml_issue_date", &cpl,
451 "<IssueDate>", "<IssueDate>x",
452 { dcp::VerificationNote::Code::INVALID_XML,
453 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
458 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
460 check_verify_result_after_replace (
461 "invalid_xml_pkl_id", &pkl,
462 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
463 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
464 { dcp::VerificationNote::Code::INVALID_XML }
469 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
471 check_verify_result_after_replace (
472 "invalix_xml_asset_map_id", &asset_map,
473 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
474 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
475 { dcp::VerificationNote::Code::INVALID_XML }
480 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
483 auto dir = setup (3, "verify_invalid_standard");
484 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
486 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
487 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
488 path const assetmap_file = dir / "ASSETMAP";
490 auto st = stages.begin();
491 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
492 BOOST_REQUIRE (st->second);
493 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
495 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
496 BOOST_REQUIRE (st->second);
497 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
499 BOOST_CHECK_EQUAL (st->first, "Checking reel");
500 BOOST_REQUIRE (!st->second);
502 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
503 BOOST_REQUIRE (st->second);
504 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
506 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
507 BOOST_REQUIRE (st->second);
508 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
510 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
511 BOOST_REQUIRE (st->second);
512 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
514 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
515 BOOST_REQUIRE (st->second);
516 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
518 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
519 BOOST_REQUIRE (st->second);
520 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
522 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
523 BOOST_REQUIRE (st->second);
524 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
526 BOOST_REQUIRE (st == stages.end());
528 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
529 auto i = notes.begin ();
530 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
531 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
534 /* DCP with a short asset */
535 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
537 auto dir = setup (8, "invalid_duration");
538 check_verify_result (
541 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
542 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
543 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
544 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
545 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
552 dcp_from_frame (dcp::ArrayData const& frame, path dir)
554 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
555 create_directories (dir);
556 auto writer = asset->start_write (dir / "pic.mxf", true);
557 for (int i = 0; i < 24; ++i) {
558 writer->write (frame.data(), frame.size());
562 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
563 return write_dcp_with_single_asset (dir, reel_asset);
567 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
569 int const too_big = 1302083 * 2;
571 /* Compress a black image */
572 auto image = black_image ();
573 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
574 BOOST_REQUIRE (frame.size() < too_big);
576 /* Place it in a bigger block with some zero padding at the end */
577 dcp::ArrayData oversized_frame(too_big);
578 memcpy (oversized_frame.data(), frame.data(), frame.size());
579 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
581 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
582 prepare_directory (dir);
583 auto cpl = dcp_from_frame (oversized_frame, dir);
585 check_verify_result (
588 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
589 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
594 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
596 int const nearly_too_big = 1302083 * 0.98;
598 /* Compress a black image */
599 auto image = black_image ();
600 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
601 BOOST_REQUIRE (frame.size() < nearly_too_big);
603 /* Place it in a bigger block with some zero padding at the end */
604 dcp::ArrayData oversized_frame(nearly_too_big);
605 memcpy (oversized_frame.data(), frame.data(), frame.size());
606 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
608 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
609 prepare_directory (dir);
610 auto cpl = dcp_from_frame (oversized_frame, dir);
612 check_verify_result (
615 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
616 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
621 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
623 /* Compress a black image */
624 auto image = black_image ();
625 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
626 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
628 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
629 prepare_directory (dir);
630 auto cpl = dcp_from_frame (frame, dir);
632 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
636 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
638 path const dir("build/test/verify_valid_interop_subtitles");
639 prepare_directory (dir);
640 copy_file ("test/data/subs1.xml", dir / "subs.xml");
641 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
642 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
643 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
645 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
649 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
651 using namespace boost::filesystem;
653 path const dir("build/test/verify_invalid_interop_subtitles");
654 prepare_directory (dir);
655 copy_file ("test/data/subs1.xml", dir / "subs.xml");
656 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
657 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
658 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
661 Editor e (dir / "subs.xml");
662 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
665 check_verify_result (
668 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
671 dcp::VerificationNote::Type::ERROR,
672 dcp::VerificationNote::Code::INVALID_XML,
673 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
681 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
683 path const dir("build/test/verify_valid_smpte_subtitles");
684 prepare_directory (dir);
685 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
686 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
687 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(25, 1), 300 * 24, 0);
688 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
690 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
694 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
696 using namespace boost::filesystem;
698 path const dir("build/test/verify_invalid_smpte_subtitles");
699 prepare_directory (dir);
700 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
701 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
702 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
703 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
705 check_verify_result (
708 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
710 dcp::VerificationNote::Type::ERROR,
711 dcp::VerificationNote::Code::INVALID_XML,
712 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
722 BOOST_AUTO_TEST_CASE (verify_external_asset)
724 path const ov_dir("build/test/verify_external_asset");
725 prepare_directory (ov_dir);
727 auto image = black_image ();
728 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
729 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
730 dcp_from_frame (frame, ov_dir);
732 dcp::DCP ov (ov_dir);
735 path const vf_dir("build/test/verify_external_asset_vf");
736 prepare_directory (vf_dir);
738 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
739 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
741 check_verify_result (
744 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
745 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
750 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
752 path const dir("build/test/verify_valid_cpl_metadata");
753 prepare_directory (dir);
755 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
756 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
757 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
759 auto reel = make_shared<dcp::Reel>();
760 reel->add (reel_asset);
762 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
763 reel->add (simple_markers(16 * 24));
765 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
767 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
768 cpl->set_main_sound_sample_rate (48000);
769 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
770 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
771 cpl->set_version_number (1);
776 dcp::Standard::SMPTE,
777 dcp::String::compose("libdcp %1", dcp::version),
778 dcp::String::compose("libdcp %1", dcp::version),
779 dcp::LocalTime().as_string(),
785 path find_cpl (path dir)
787 for (auto i: directory_iterator(dir)) {
788 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
793 BOOST_REQUIRE (false);
798 /* DCP with invalid CompositionMetadataAsset */
799 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
801 using namespace boost::filesystem;
803 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
804 prepare_directory (dir);
806 auto reel = make_shared<dcp::Reel>();
807 reel->add (black_picture_asset(dir));
808 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
810 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
811 cpl->set_main_sound_sample_rate (48000);
812 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
813 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
814 cpl->set_version_number (1);
816 reel->add (simple_markers());
821 dcp::Standard::SMPTE,
822 dcp::String::compose("libdcp %1", dcp::version),
823 dcp::String::compose("libdcp %1", dcp::version),
824 dcp::LocalTime().as_string(),
829 Editor e (find_cpl(dir));
830 e.replace ("MainSound", "MainSoundX");
833 check_verify_result (
836 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
837 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
839 dcp::VerificationNote::Type::ERROR,
840 dcp::VerificationNote::Code::INVALID_XML,
841 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
842 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
843 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
844 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
845 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
846 "ExtensionMetadataList?,)'"),
847 canonical(cpl->file().get()),
850 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
855 /* DCP with invalid CompositionMetadataAsset */
856 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
858 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
859 prepare_directory (dir);
861 auto reel = make_shared<dcp::Reel>();
862 reel->add (black_picture_asset(dir));
863 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
865 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
866 cpl->set_main_sound_sample_rate (48000);
867 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
868 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
873 dcp::Standard::SMPTE,
874 dcp::String::compose("libdcp %1", dcp::version),
875 dcp::String::compose("libdcp %1", dcp::version),
876 dcp::LocalTime().as_string(),
881 Editor e (find_cpl(dir));
882 e.replace ("meta:Width", "meta:WidthX");
885 check_verify_result (
887 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
892 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
894 path const dir("build/test/verify_invalid_language1");
895 prepare_directory (dir);
896 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
897 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
898 asset->_language = "wrong-andbad";
899 asset->write (dir / "subs.mxf");
900 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
901 reel_asset->_language = "badlang";
902 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
904 check_verify_result (
907 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
908 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
909 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
914 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
915 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
917 path const dir("build/test/verify_invalid_language2");
918 prepare_directory (dir);
919 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
920 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
921 asset->_language = "wrong-andbad";
922 asset->write (dir / "subs.mxf");
923 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
924 reel_asset->_language = "badlang";
925 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
927 check_verify_result (
930 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
931 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
932 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
937 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
938 * the release territory.
940 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
942 path const dir("build/test/verify_invalid_language3");
943 prepare_directory (dir);
945 auto picture = simple_picture (dir, "foo");
946 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
947 auto reel = make_shared<dcp::Reel>();
948 reel->add (reel_picture);
949 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
950 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
951 reel->add (reel_sound);
952 reel->add (simple_markers());
954 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
956 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
957 cpl->_additional_subtitle_languages.push_back("andso-is-this");
958 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
959 cpl->set_main_sound_sample_rate (48000);
960 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
961 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
962 cpl->set_version_number (1);
963 cpl->_release_territory = "fred-jim";
964 auto dcp = make_shared<dcp::DCP>(dir);
967 dcp::Standard::SMPTE,
968 dcp::String::compose("libdcp %1", dcp::version),
969 dcp::String::compose("libdcp %1", dcp::version),
970 dcp::LocalTime().as_string(),
974 check_verify_result (
977 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
986 vector<dcp::VerificationNote>
987 check_picture_size (int width, int height, int frame_rate, bool three_d)
989 using namespace boost::filesystem;
991 path dcp_path = "build/test/verify_picture_test";
992 prepare_directory (dcp_path);
994 shared_ptr<dcp::PictureAsset> mp;
996 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
998 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1000 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1002 auto image = black_image (dcp::Size(width, height));
1003 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1004 int const length = three_d ? frame_rate * 2 : frame_rate;
1005 for (int i = 0; i < length; ++i) {
1006 picture_writer->write (j2c.data(), j2c.size());
1008 picture_writer->finalize ();
1010 auto d = make_shared<dcp::DCP>(dcp_path);
1011 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1012 cpl->set_annotation_text ("A Test DCP");
1013 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1014 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1015 cpl->set_main_sound_sample_rate (48000);
1016 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1017 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1018 cpl->set_version_number (1);
1020 auto reel = make_shared<dcp::Reel>();
1023 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1025 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1028 reel->add (simple_markers(frame_rate));
1034 dcp::Standard::SMPTE,
1035 dcp::String::compose("libdcp %1", dcp::version),
1036 dcp::String::compose("libdcp %1", dcp::version),
1037 dcp::LocalTime().as_string(),
1041 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1047 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1049 auto notes = check_picture_size(width, height, frame_rate, three_d);
1051 BOOST_CHECK_EQUAL (notes.size(), 0U);
1057 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1059 auto notes = check_picture_size(width, height, frame_rate, three_d);
1060 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1061 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1062 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1068 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1070 auto notes = check_picture_size(width, height, frame_rate, three_d);
1071 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1072 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1073 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1079 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1081 auto notes = check_picture_size(width, height, frame_rate, three_d);
1082 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1083 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1084 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1088 BOOST_AUTO_TEST_CASE (verify_picture_size)
1090 using namespace boost::filesystem;
1093 check_picture_size_ok (2048, 858, 24, false);
1094 check_picture_size_ok (2048, 858, 25, false);
1095 check_picture_size_ok (2048, 858, 48, false);
1096 check_picture_size_ok (2048, 858, 24, true);
1097 check_picture_size_ok (2048, 858, 25, true);
1098 check_picture_size_ok (2048, 858, 48, true);
1101 check_picture_size_ok (1998, 1080, 24, false);
1102 check_picture_size_ok (1998, 1080, 25, false);
1103 check_picture_size_ok (1998, 1080, 48, false);
1104 check_picture_size_ok (1998, 1080, 24, true);
1105 check_picture_size_ok (1998, 1080, 25, true);
1106 check_picture_size_ok (1998, 1080, 48, true);
1109 check_picture_size_ok (4096, 1716, 24, false);
1112 check_picture_size_ok (3996, 2160, 24, false);
1114 /* Bad frame size */
1115 check_picture_size_bad_frame_size (2050, 858, 24, false);
1116 check_picture_size_bad_frame_size (2048, 658, 25, false);
1117 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1118 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1120 /* Bad 2K frame rate */
1121 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1122 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1123 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1125 /* Bad 4K frame rate */
1126 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1127 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1130 auto notes = check_picture_size(3996, 2160, 24, true);
1131 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1132 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1133 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1139 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1142 make_shared<dcp::SubtitleString>(
1150 dcp::Time(start_frame, 24, 24),
1151 dcp::Time(end_frame, 24, 24),
1153 dcp::HAlign::CENTER,
1155 dcp::VAlign::CENTER,
1156 dcp::Direction::LTR,
1167 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1169 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1170 prepare_directory (dir);
1172 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1173 for (int i = 0; i < 2048; ++i) {
1174 add_test_subtitle (asset, i * 24, i * 24 + 20);
1176 asset->set_language (dcp::LanguageTag("de-DE"));
1177 asset->write (dir / "subs.mxf");
1178 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 2049 * 24, 0);
1179 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1181 check_verify_result (
1184 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1186 dcp::VerificationNote::Type::BV21_ERROR,
1187 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1189 canonical(dir / "subs.mxf")
1191 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1192 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1198 shared_ptr<dcp::SMPTESubtitleAsset>
1199 make_large_subtitle_asset (path font_file)
1201 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1202 dcp::ArrayData big_fake_font(1024 * 1024);
1203 big_fake_font.write (font_file);
1204 for (int i = 0; i < 116; ++i) {
1205 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1213 verify_timed_text_asset_too_large (string name)
1215 auto const dir = path("build/test") / name;
1216 prepare_directory (dir);
1217 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1218 add_test_subtitle (asset, 0, 20);
1219 asset->set_language (dcp::LanguageTag("de-DE"));
1220 asset->write (dir / "subs.mxf");
1222 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1223 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1225 check_verify_result (
1228 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1229 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1231 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1237 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1239 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1240 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1244 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1246 path dir = "build/test/verify_missing_subtitle_language";
1247 prepare_directory (dir);
1248 auto dcp = make_simple (dir, 1, 240);
1251 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1252 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1253 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1254 "<ContentTitleText>Content</ContentTitleText>"
1255 "<AnnotationText>Annotation</AnnotationText>"
1256 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1257 "<ReelNumber>1</ReelNumber>"
1258 "<EditRate>25 1</EditRate>"
1259 "<TimeCodeRate>25</TimeCodeRate>"
1260 "<StartTime>00:00:00:00</StartTime>"
1261 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1263 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1264 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1265 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1271 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1272 BOOST_REQUIRE (xml_file);
1273 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1275 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1276 subs->write (dir / "subs.mxf");
1278 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1279 dcp->cpls().front()->reels().front()->add(reel_subs);
1281 dcp::Standard::SMPTE,
1282 dcp::String::compose("libdcp %1", dcp::version),
1283 dcp::String::compose("libdcp %1", dcp::version),
1284 dcp::LocalTime().as_string(),
1288 check_verify_result (
1291 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1292 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1297 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1299 path path ("build/test/verify_mismatched_subtitle_languages");
1300 auto dcp = make_simple (path, 2, 240);
1301 auto cpl = dcp->cpls()[0];
1304 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1305 subs->set_language (dcp::LanguageTag("de-DE"));
1306 subs->add (simple_subtitle());
1307 subs->write (path / "subs1.mxf");
1308 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1309 cpl->reels()[0]->add(reel_subs);
1313 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1314 subs->set_language (dcp::LanguageTag("en-US"));
1315 subs->add (simple_subtitle());
1316 subs->write (path / "subs2.mxf");
1317 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1318 cpl->reels()[1]->add(reel_subs);
1322 dcp::Standard::SMPTE,
1323 dcp::String::compose("libdcp %1", dcp::version),
1324 dcp::String::compose("libdcp %1", dcp::version),
1325 dcp::LocalTime().as_string(),
1329 check_verify_result (
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1339 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1341 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1342 auto dcp = make_simple (path, 2, 240);
1343 auto cpl = dcp->cpls()[0];
1346 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1347 ccaps->set_language (dcp::LanguageTag("de-DE"));
1348 ccaps->add (simple_subtitle());
1349 ccaps->write (path / "subs1.mxf");
1350 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1351 cpl->reels()[0]->add(reel_ccaps);
1355 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1356 ccaps->set_language (dcp::LanguageTag("en-US"));
1357 ccaps->add (simple_subtitle());
1358 ccaps->write (path / "subs2.mxf");
1359 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1360 cpl->reels()[1]->add(reel_ccaps);
1364 dcp::Standard::SMPTE,
1365 dcp::String::compose("libdcp %1", dcp::version),
1366 dcp::String::compose("libdcp %1", dcp::version),
1367 dcp::LocalTime().as_string(),
1371 check_verify_result (
1374 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1375 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1380 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1382 path dir = "build/test/verify_missing_subtitle_start_time";
1383 prepare_directory (dir);
1384 auto dcp = make_simple (dir, 1, 240);
1387 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1388 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1389 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1390 "<ContentTitleText>Content</ContentTitleText>"
1391 "<AnnotationText>Annotation</AnnotationText>"
1392 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1393 "<ReelNumber>1</ReelNumber>"
1394 "<Language>de-DE</Language>"
1395 "<EditRate>25 1</EditRate>"
1396 "<TimeCodeRate>25</TimeCodeRate>"
1397 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1399 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1400 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1401 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1407 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1408 BOOST_REQUIRE (xml_file);
1409 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1411 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1412 subs->write (dir / "subs.mxf");
1414 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1415 dcp->cpls().front()->reels().front()->add(reel_subs);
1417 dcp::Standard::SMPTE,
1418 dcp::String::compose("libdcp %1", dcp::version),
1419 dcp::String::compose("libdcp %1", dcp::version),
1420 dcp::LocalTime().as_string(),
1424 check_verify_result (
1427 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1428 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1433 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1435 path dir = "build/test/verify_invalid_subtitle_start_time";
1436 prepare_directory (dir);
1437 auto dcp = make_simple (dir, 1, 240);
1440 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1441 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1442 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1443 "<ContentTitleText>Content</ContentTitleText>"
1444 "<AnnotationText>Annotation</AnnotationText>"
1445 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1446 "<ReelNumber>1</ReelNumber>"
1447 "<Language>de-DE</Language>"
1448 "<EditRate>25 1</EditRate>"
1449 "<TimeCodeRate>25</TimeCodeRate>"
1450 "<StartTime>00:00:02:00</StartTime>"
1451 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1453 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1454 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1455 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1461 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1462 BOOST_REQUIRE (xml_file);
1463 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1465 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1466 subs->write (dir / "subs.mxf");
1468 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1469 dcp->cpls().front()->reels().front()->add(reel_subs);
1471 dcp::Standard::SMPTE,
1472 dcp::String::compose("libdcp %1", dcp::version),
1473 dcp::String::compose("libdcp %1", dcp::version),
1474 dcp::LocalTime().as_string(),
1478 check_verify_result (
1481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1482 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1490 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1493 , v_position(v_position_)
1505 shared_ptr<dcp::CPL>
1506 dcp_with_text (path dir, vector<TestText> subs)
1508 prepare_directory (dir);
1509 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1510 asset->set_start_time (dcp::Time());
1511 for (auto i: subs) {
1512 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1514 asset->set_language (dcp::LanguageTag("de-DE"));
1515 asset->write (dir / "subs.mxf");
1517 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1518 return write_dcp_with_single_asset (dir, reel_asset);
1522 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1524 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1525 /* Just too early */
1526 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1527 check_verify_result (
1530 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1531 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1537 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1539 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1540 /* Just late enough */
1541 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1542 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1546 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1548 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1549 prepare_directory (dir);
1551 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1552 asset1->set_start_time (dcp::Time());
1553 /* Just late enough */
1554 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1555 asset1->set_language (dcp::LanguageTag("de-DE"));
1556 asset1->write (dir / "subs1.mxf");
1557 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1558 auto reel1 = make_shared<dcp::Reel>();
1559 reel1->add (reel_asset1);
1560 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1561 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1562 reel1->add (markers1);
1564 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1565 asset2->set_start_time (dcp::Time());
1566 /* This would be too early on first reel but should be OK on the second */
1567 add_test_subtitle (asset2, 0, 4 * 24);
1568 asset2->set_language (dcp::LanguageTag("de-DE"));
1569 asset2->write (dir / "subs2.mxf");
1570 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1571 auto reel2 = make_shared<dcp::Reel>();
1572 reel2->add (reel_asset2);
1573 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1574 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1575 reel2->add (markers2);
1577 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1580 auto dcp = make_shared<dcp::DCP>(dir);
1583 dcp::Standard::SMPTE,
1584 dcp::String::compose("libdcp %1", dcp::version),
1585 dcp::String::compose("libdcp %1", dcp::version),
1586 dcp::LocalTime().as_string(),
1591 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1595 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1597 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1598 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1602 { 5 * 24 + 1, 6 * 24 },
1604 check_verify_result (
1607 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1608 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1613 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1615 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1616 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1620 { 5 * 24 + 16, 8 * 24 },
1622 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1626 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1628 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1629 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1630 check_verify_result (
1633 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1634 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1639 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1641 auto const dir = path("build/test/verify_valid_subtitle_duration");
1642 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1643 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1647 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1649 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1650 prepare_directory (dir);
1651 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1652 asset->set_start_time (dcp::Time());
1653 add_test_subtitle (asset, 0, 4 * 24);
1654 asset->set_language (dcp::LanguageTag("de-DE"));
1655 asset->write (dir / "subs.mxf");
1657 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1658 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1659 check_verify_result (
1662 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1663 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1664 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1670 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1672 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1673 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1676 { 96, 200, 0.0, "We" },
1677 { 96, 200, 0.1, "have" },
1678 { 96, 200, 0.2, "four" },
1679 { 96, 200, 0.3, "lines" }
1681 check_verify_result (
1684 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1690 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1692 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1693 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1696 { 96, 200, 0.0, "We" },
1697 { 96, 200, 0.1, "have" },
1698 { 96, 200, 0.2, "four" },
1700 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1704 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1706 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1707 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1710 { 96, 300, 0.0, "We" },
1711 { 96, 300, 0.1, "have" },
1712 { 150, 180, 0.2, "four" },
1713 { 150, 180, 0.3, "lines" }
1715 check_verify_result (
1718 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1719 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1724 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1726 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1727 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1730 { 96, 300, 0.0, "We" },
1731 { 96, 300, 0.1, "have" },
1732 { 150, 180, 0.2, "four" },
1733 { 190, 250, 0.3, "lines" }
1735 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1739 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1741 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1742 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1745 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1756 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1758 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1759 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1762 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1764 check_verify_result (
1767 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1768 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1773 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1775 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1776 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1779 { 96, 200, 0.0, "We" },
1780 { 96, 200, 0.1, "have" },
1781 { 96, 200, 0.2, "four" },
1782 { 96, 200, 0.3, "lines" }
1784 check_verify_result (
1787 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1788 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1793 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1795 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1796 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1799 { 96, 200, 0.0, "We" },
1800 { 96, 200, 0.1, "have" },
1801 { 96, 200, 0.2, "four" },
1803 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1807 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1809 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1810 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1813 { 96, 300, 0.0, "We" },
1814 { 96, 300, 0.1, "have" },
1815 { 150, 180, 0.2, "four" },
1816 { 150, 180, 0.3, "lines" }
1818 check_verify_result (
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1822 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1827 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1829 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1830 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1833 { 96, 300, 0.0, "We" },
1834 { 96, 300, 0.1, "have" },
1835 { 150, 180, 0.2, "four" },
1836 { 190, 250, 0.3, "lines" }
1838 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1842 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1844 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1845 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1848 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1859 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1861 path const dir("build/test/verify_invalid_sound_frame_rate");
1862 prepare_directory (dir);
1864 auto picture = simple_picture (dir, "foo");
1865 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1866 auto reel = make_shared<dcp::Reel>();
1867 reel->add (reel_picture);
1868 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1869 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1870 reel->add (reel_sound);
1871 reel->add (simple_markers());
1872 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1874 auto dcp = make_shared<dcp::DCP>(dir);
1877 dcp::Standard::SMPTE,
1878 dcp::String::compose("libdcp %1", dcp::version),
1879 dcp::String::compose("libdcp %1", dcp::version),
1880 dcp::LocalTime().as_string(),
1884 check_verify_result (
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1893 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1895 path const dir("build/test/verify_missing_cpl_annotation_text");
1896 auto dcp = make_simple (dir);
1898 dcp::Standard::SMPTE,
1899 dcp::String::compose("libdcp %1", dcp::version),
1900 dcp::String::compose("libdcp %1", dcp::version),
1901 dcp::LocalTime().as_string(),
1905 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1907 auto const cpl = dcp->cpls()[0];
1910 BOOST_REQUIRE (cpl->file());
1911 Editor e(cpl->file().get());
1912 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1915 check_verify_result (
1918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1919 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1924 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1926 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1927 auto dcp = make_simple (dir);
1929 dcp::Standard::SMPTE,
1930 dcp::String::compose("libdcp %1", dcp::version),
1931 dcp::String::compose("libdcp %1", dcp::version),
1932 dcp::LocalTime().as_string(),
1936 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1937 auto const cpl = dcp->cpls()[0];
1940 BOOST_REQUIRE (cpl->file());
1941 Editor e(cpl->file().get());
1942 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1945 check_verify_result (
1948 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1949 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1954 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1956 path const dir("build/test/verify_mismatched_asset_duration");
1957 prepare_directory (dir);
1958 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1959 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1961 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1962 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1964 auto reel = make_shared<dcp::Reel>(
1965 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1966 make_shared<dcp::ReelSoundAsset>(ms, 0)
1969 reel->add (simple_markers());
1974 dcp::Standard::SMPTE,
1975 dcp::String::compose("libdcp %1", dcp::version),
1976 dcp::String::compose("libdcp %1", dcp::version),
1977 dcp::LocalTime().as_string(),
1981 check_verify_result (
1984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1992 shared_ptr<dcp::CPL>
1993 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1995 prepare_directory (dir);
1996 auto dcp = make_shared<dcp::DCP>(dir);
1997 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1999 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2000 subs->set_language (dcp::LanguageTag("de-DE"));
2001 subs->set_start_time (dcp::Time());
2002 subs->add (simple_subtitle());
2003 subs->write (dir / "subs.mxf");
2004 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
2006 auto reel1 = make_shared<dcp::Reel>(
2007 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2008 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2012 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2015 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2016 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2017 reel1->add (markers1);
2021 auto reel2 = make_shared<dcp::Reel>(
2022 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2023 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2027 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2030 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2031 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2032 reel2->add (markers2);
2038 dcp::Standard::SMPTE,
2039 dcp::String::compose("libdcp %1", dcp::version),
2040 dcp::String::compose("libdcp %1", dcp::version),
2041 dcp::LocalTime().as_string(),
2049 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2052 path dir ("build/test/missing_main_subtitle_from_some_reels");
2053 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2054 check_verify_result (
2057 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2058 { 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_reels2");
2065 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2066 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2070 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2071 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2072 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2078 shared_ptr<dcp::CPL>
2079 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2081 prepare_directory (dir);
2082 auto dcp = make_shared<dcp::DCP>(dir);
2083 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2085 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2086 subs->set_language (dcp::LanguageTag("de-DE"));
2087 subs->set_start_time (dcp::Time());
2088 subs->add (simple_subtitle());
2089 subs->write (dir / "subs.mxf");
2091 auto reel1 = make_shared<dcp::Reel>(
2092 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2093 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2096 for (int i = 0; i < caps_in_reel1; ++i) {
2097 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2100 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2101 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2102 reel1->add (markers1);
2106 auto reel2 = make_shared<dcp::Reel>(
2107 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2108 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2111 for (int i = 0; i < caps_in_reel2; ++i) {
2112 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2115 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2116 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2117 reel2->add (markers2);
2123 dcp::Standard::SMPTE,
2124 dcp::String::compose("libdcp %1", dcp::version),
2125 dcp::String::compose("libdcp %1", dcp::version),
2126 dcp::LocalTime().as_string(),
2134 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2137 path dir ("build/test/mismatched_closed_caption_asset_counts");
2138 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2139 check_verify_result (
2142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2143 { 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_reels2");
2149 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2150 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2154 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2155 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2156 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2163 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2165 prepare_directory (dir);
2166 auto dcp = make_shared<dcp::DCP>(dir);
2167 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2169 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2170 subs->set_language (dcp::LanguageTag("de-DE"));
2171 subs->set_start_time (dcp::Time());
2172 subs->add (simple_subtitle());
2173 subs->write (dir / "subs.mxf");
2174 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2177 auto reel = make_shared<dcp::Reel>(
2178 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2179 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2182 reel->add (reel_text);
2184 reel->add (simple_markers(240));
2190 dcp::Standard::SMPTE,
2191 dcp::String::compose("libdcp %1", dcp::version),
2192 dcp::String::compose("libdcp %1", dcp::version),
2193 dcp::LocalTime().as_string(),
2197 check_verify_result (
2200 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2201 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2206 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2208 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2209 "build/test/verify_subtitle_entry_point_must_be_present",
2210 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2211 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2212 asset->unset_entry_point ();
2216 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2217 "build/test/verify_subtitle_entry_point_must_be_zero",
2218 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2219 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2220 asset->set_entry_point (4);
2224 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2225 "build/test/verify_closed_caption_entry_point_must_be_present",
2226 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2227 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2228 asset->unset_entry_point ();
2232 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2233 "build/test/verify_closed_caption_entry_point_must_be_zero",
2234 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2235 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2236 asset->set_entry_point (9);
2242 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2246 path const dir("build/test/verify_missing_hash");
2247 auto dcp = make_simple (dir);
2249 dcp::Standard::SMPTE,
2250 dcp::String::compose("libdcp %1", dcp::version),
2251 dcp::String::compose("libdcp %1", dcp::version),
2252 dcp::LocalTime().as_string(),
2256 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2257 auto const cpl = dcp->cpls()[0];
2260 BOOST_REQUIRE (cpl->file());
2261 Editor e(cpl->file().get());
2262 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2265 check_verify_result (
2268 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2269 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2276 verify_markers_test (
2278 vector<pair<dcp::Marker, dcp::Time>> markers,
2279 vector<dcp::VerificationNote> test_notes
2282 auto dcp = make_simple (dir);
2283 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2284 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2285 for (auto const& i: markers) {
2286 markers_asset->set (i.first, i.second);
2288 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2290 dcp::Standard::SMPTE,
2291 dcp::String::compose("libdcp %1", dcp::version),
2292 dcp::String::compose("libdcp %1", dcp::version),
2293 dcp::LocalTime().as_string(),
2297 check_verify_result ({dir}, test_notes);
2301 BOOST_AUTO_TEST_CASE (verify_markers)
2303 verify_markers_test (
2304 "build/test/verify_markers_all_correct",
2306 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2307 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2308 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2309 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2314 verify_markers_test (
2315 "build/test/verify_markers_missing_ffec",
2317 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2318 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2319 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2322 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2325 verify_markers_test (
2326 "build/test/verify_markers_missing_ffmc",
2328 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2329 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2330 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2336 verify_markers_test (
2337 "build/test/verify_markers_missing_ffoc",
2339 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2340 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2341 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2344 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2347 verify_markers_test (
2348 "build/test/verify_markers_missing_lfoc",
2350 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2351 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2352 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2355 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2358 verify_markers_test (
2359 "build/test/verify_markers_incorrect_ffoc",
2361 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2362 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2363 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2364 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2367 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2370 verify_markers_test (
2371 "build/test/verify_markers_incorrect_lfoc",
2373 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2374 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2375 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2376 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2379 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2384 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2386 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2387 prepare_directory (dir);
2388 auto dcp = make_simple (dir);
2389 auto cpl = dcp->cpls()[0];
2390 cpl->unset_version_number();
2392 dcp::Standard::SMPTE,
2393 dcp::String::compose("libdcp %1", dcp::version),
2394 dcp::String::compose("libdcp %1", dcp::version),
2395 dcp::LocalTime().as_string(),
2399 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2403 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2405 path dir = "build/test/verify_missing_extension_metadata1";
2406 auto dcp = make_simple (dir);
2408 dcp::Standard::SMPTE,
2409 dcp::String::compose("libdcp %1", dcp::version),
2410 dcp::String::compose("libdcp %1", dcp::version),
2411 dcp::LocalTime().as_string(),
2415 auto cpl = dcp->cpls()[0];
2418 Editor e (cpl->file().get());
2419 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2422 check_verify_result (
2425 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2426 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2431 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2433 path dir = "build/test/verify_missing_extension_metadata2";
2434 auto dcp = make_simple (dir);
2436 dcp::Standard::SMPTE,
2437 dcp::String::compose("libdcp %1", dcp::version),
2438 dcp::String::compose("libdcp %1", dcp::version),
2439 dcp::LocalTime().as_string(),
2443 auto cpl = dcp->cpls()[0];
2446 Editor e (cpl->file().get());
2447 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2450 check_verify_result (
2453 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2454 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2459 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2461 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2462 auto dcp = make_simple (dir);
2464 dcp::Standard::SMPTE,
2465 dcp::String::compose("libdcp %1", dcp::version),
2466 dcp::String::compose("libdcp %1", dcp::version),
2467 dcp::LocalTime().as_string(),
2471 auto const cpl = dcp->cpls()[0];
2474 Editor e (cpl->file().get());
2475 e.replace ("<meta:Name>A", "<meta:NameX>A");
2476 e.replace ("n</meta:Name>", "n</meta:NameX>");
2479 check_verify_result (
2482 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2483 { 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 },
2484 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2489 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2491 path dir = "build/test/verify_invalid_extension_metadata1";
2492 auto dcp = make_simple (dir);
2494 dcp::Standard::SMPTE,
2495 dcp::String::compose("libdcp %1", dcp::version),
2496 dcp::String::compose("libdcp %1", dcp::version),
2497 dcp::LocalTime().as_string(),
2501 auto cpl = dcp->cpls()[0];
2504 Editor e (cpl->file().get());
2505 e.replace ("Application", "Fred");
2508 check_verify_result (
2511 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2512 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2517 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2519 path dir = "build/test/verify_invalid_extension_metadata2";
2520 auto dcp = make_simple (dir);
2522 dcp::Standard::SMPTE,
2523 dcp::String::compose("libdcp %1", dcp::version),
2524 dcp::String::compose("libdcp %1", dcp::version),
2525 dcp::LocalTime().as_string(),
2529 auto cpl = dcp->cpls()[0];
2532 Editor e (cpl->file().get());
2533 e.replace ("DCP Constraints Profile", "Fred");
2536 check_verify_result (
2539 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2540 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2545 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2547 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2548 auto dcp = make_simple (dir);
2550 dcp::Standard::SMPTE,
2551 dcp::String::compose("libdcp %1", dcp::version),
2552 dcp::String::compose("libdcp %1", dcp::version),
2553 dcp::LocalTime().as_string(),
2557 auto const cpl = dcp->cpls()[0];
2560 Editor e (cpl->file().get());
2561 e.replace ("<meta:Value>", "<meta:ValueX>");
2562 e.replace ("</meta:Value>", "</meta:ValueX>");
2565 check_verify_result (
2568 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2569 { 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 },
2570 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2575 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2577 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2578 auto dcp = make_simple (dir);
2580 dcp::Standard::SMPTE,
2581 dcp::String::compose("libdcp %1", dcp::version),
2582 dcp::String::compose("libdcp %1", dcp::version),
2583 dcp::LocalTime().as_string(),
2587 auto const cpl = dcp->cpls()[0];
2590 Editor e (cpl->file().get());
2591 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2594 check_verify_result (
2597 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2598 { 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() },
2603 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2605 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2606 auto dcp = make_simple (dir);
2608 dcp::Standard::SMPTE,
2609 dcp::String::compose("libdcp %1", dcp::version),
2610 dcp::String::compose("libdcp %1", dcp::version),
2611 dcp::LocalTime().as_string(),
2615 auto const cpl = dcp->cpls()[0];
2618 Editor e (cpl->file().get());
2619 e.replace ("<meta:Property>", "<meta:PropertyX>");
2620 e.replace ("</meta:Property>", "</meta:PropertyX>");
2623 check_verify_result (
2626 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2627 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2628 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2633 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2635 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2636 auto dcp = make_simple (dir);
2638 dcp::Standard::SMPTE,
2639 dcp::String::compose("libdcp %1", dcp::version),
2640 dcp::String::compose("libdcp %1", dcp::version),
2641 dcp::LocalTime().as_string(),
2645 auto const cpl = dcp->cpls()[0];
2648 Editor e (cpl->file().get());
2649 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2650 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2653 check_verify_result (
2656 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2657 { 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 },
2658 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2664 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2666 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2667 prepare_directory (dir);
2668 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2669 copy_file (i.path(), dir / i.path().filename());
2672 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2673 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2677 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2680 check_verify_result (
2683 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2684 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2686 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2687 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2690 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2695 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2697 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2698 prepare_directory (dir);
2699 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2700 copy_file (i.path(), dir / i.path().filename());
2703 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2704 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2707 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2710 check_verify_result (
2713 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2714 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2715 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2716 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2717 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2718 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2719 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2724 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2726 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2727 prepare_directory (dir);
2728 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2729 copy_file (i.path(), dir / i.path().filename());
2733 Editor e (dir / dcp_test1_pkl);
2734 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2737 check_verify_result ({dir}, {});
2741 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2743 path dir ("build/test/verify_must_not_be_partially_encrypted");
2744 prepare_directory (dir);
2748 auto signer = make_shared<dcp::CertificateChain>();
2749 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2750 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2751 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2752 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2754 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2758 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2761 auto writer = mp->start_write (dir / "video.mxf", false);
2762 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2763 for (int i = 0; i < 24; ++i) {
2764 writer->write (j2c.data(), j2c.size());
2766 writer->finalize ();
2768 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2770 auto reel = make_shared<dcp::Reel>(
2771 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2772 make_shared<dcp::ReelSoundAsset>(ms, 0)
2775 reel->add (simple_markers());
2779 cpl->set_content_version (
2780 {"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"}
2782 cpl->set_annotation_text ("A Test DCP");
2783 cpl->set_issuer ("OpenDCP 0.0.25");
2784 cpl->set_creator ("OpenDCP 0.0.25");
2785 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2786 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2787 cpl->set_main_sound_sample_rate (48000);
2788 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2789 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2790 cpl->set_version_number (1);
2794 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);
2796 check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});
2800 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2802 vector<dcp::VerificationNote> notes;
2803 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"));
2804 auto reader = picture.start_read ();
2805 auto frame = reader->get_frame (0);
2806 verify_j2k (frame, notes);
2811 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2813 vector<dcp::VerificationNote> notes;
2814 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2815 auto reader = picture.start_read ();
2816 auto frame = reader->get_frame (0);
2817 verify_j2k (frame, notes);
2822 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2824 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2825 prepare_directory (dir);
2826 auto dcp = make_simple (dir);
2827 dcp->write_xml (dcp::Standard::SMPTE);
2828 vector<dcp::VerificationNote> notes;
2829 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2830 auto reader = picture.start_read ();
2831 auto frame = reader->get_frame (0);
2832 verify_j2k (frame, notes);