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);
209 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
210 for (auto i = 0U; i < notes.size(); ++i) {
211 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
218 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
220 auto dir = setup (1, suffix);
223 Editor e (file(suffix));
224 e.replace (from, to);
227 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
229 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
230 auto i = notes.begin();
231 auto j = codes.begin();
232 while (i != notes.end()) {
233 BOOST_CHECK_EQUAL (i->code(), *j);
240 BOOST_AUTO_TEST_CASE (verify_no_error)
243 auto dir = setup (1, "no_error");
244 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
246 path const cpl_file = dir / dcp_test1_cpl;
247 path const pkl_file = dir / dcp_test1_pkl;
248 path const assetmap_file = dir / "ASSETMAP.xml";
250 auto st = stages.begin();
251 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
252 BOOST_REQUIRE (st->second);
253 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
255 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
256 BOOST_REQUIRE (st->second);
257 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
259 BOOST_CHECK_EQUAL (st->first, "Checking reel");
260 BOOST_REQUIRE (!st->second);
262 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
263 BOOST_REQUIRE (st->second);
264 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
266 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
267 BOOST_REQUIRE (st->second);
268 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
270 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
271 BOOST_REQUIRE (st->second);
272 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
274 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
275 BOOST_REQUIRE (st->second);
276 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
278 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
279 BOOST_REQUIRE (st->second);
280 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
282 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
283 BOOST_REQUIRE (st->second);
284 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
286 BOOST_REQUIRE (st == stages.end());
288 BOOST_CHECK_EQUAL (notes.size(), 0);
292 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
294 using namespace boost::filesystem;
296 auto dir = setup (1, "incorrect_picture_sound_hash");
298 auto video_path = path(dir / "video.mxf");
299 auto mod = fopen(video_path.string().c_str(), "r+b");
301 fseek (mod, 4096, SEEK_SET);
303 fwrite (&x, sizeof(x), 1, mod);
306 auto audio_path = path(dir / "audio.mxf");
307 mod = fopen(audio_path.string().c_str(), "r+b");
309 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
310 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
313 dcp::ASDCPErrorSuspender sus;
314 check_verify_result (
317 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
318 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
323 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
325 using namespace boost::filesystem;
327 auto dir = setup (1, "mismatched_picture_sound_hashes");
330 Editor e (dir / dcp_test1_pkl);
331 e.replace ("<Hash>", "<Hash>x");
334 check_verify_result (
337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
338 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
342 { 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 }
347 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
349 auto dir = setup (1, "failed_read_content_kind");
352 Editor e (dir / dcp_test1_cpl);
353 e.replace ("<ContentKind>", "<ContentKind>x");
356 check_verify_result (
358 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
367 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
375 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
381 asset_map (string suffix)
383 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
387 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
389 check_verify_result_after_replace (
390 "invalid_picture_frame_rate", &cpl,
391 "<FrameRate>24 1", "<FrameRate>99 1",
392 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
393 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
397 BOOST_AUTO_TEST_CASE (verify_missing_asset)
399 auto dir = setup (1, "missing_asset");
400 remove (dir / "video.mxf");
401 check_verify_result (
404 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
409 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
411 check_verify_result_after_replace (
412 "empty_asset_path", &asset_map,
413 "<Path>video.mxf</Path>", "<Path></Path>",
414 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
419 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
421 check_verify_result_after_replace (
422 "mismatched_standard", &cpl,
423 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
424 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
425 dcp::VerificationNote::Code::INVALID_XML,
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::MISMATCHED_CPL_HASHES }
435 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
437 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
438 check_verify_result_after_replace (
439 "invalid_xml_cpl_id", &cpl,
440 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
441 { dcp::VerificationNote::Code::INVALID_XML }
446 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
448 check_verify_result_after_replace (
449 "invalid_xml_issue_date", &cpl,
450 "<IssueDate>", "<IssueDate>x",
451 { dcp::VerificationNote::Code::INVALID_XML,
452 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
457 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
459 check_verify_result_after_replace (
460 "invalid_xml_pkl_id", &pkl,
461 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
462 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
463 { dcp::VerificationNote::Code::INVALID_XML }
468 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
470 check_verify_result_after_replace (
471 "invalix_xml_asset_map_id", &asset_map,
472 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
473 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
474 { dcp::VerificationNote::Code::INVALID_XML }
479 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
482 auto dir = setup (3, "verify_invalid_standard");
483 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
485 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
486 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
487 path const assetmap_file = dir / "ASSETMAP";
489 auto st = stages.begin();
490 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
491 BOOST_REQUIRE (st->second);
492 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
494 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
495 BOOST_REQUIRE (st->second);
496 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
498 BOOST_CHECK_EQUAL (st->first, "Checking reel");
499 BOOST_REQUIRE (!st->second);
501 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
502 BOOST_REQUIRE (st->second);
503 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
505 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
506 BOOST_REQUIRE (st->second);
507 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
509 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
510 BOOST_REQUIRE (st->second);
511 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
513 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
514 BOOST_REQUIRE (st->second);
515 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
517 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
518 BOOST_REQUIRE (st->second);
519 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
521 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
522 BOOST_REQUIRE (st->second);
523 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
525 BOOST_REQUIRE (st == stages.end());
527 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
528 auto i = notes.begin ();
529 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
530 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
532 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
533 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
536 /* DCP with a short asset */
537 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
539 auto dir = setup (8, "invalid_duration");
540 check_verify_result (
543 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
544 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
545 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
546 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
547 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
548 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
555 dcp_from_frame (dcp::ArrayData const& frame, path dir)
557 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
558 create_directories (dir);
559 auto writer = asset->start_write (dir / "pic.mxf", true);
560 for (int i = 0; i < 24; ++i) {
561 writer->write (frame.data(), frame.size());
565 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
566 return write_dcp_with_single_asset (dir, reel_asset);
570 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
572 int const too_big = 1302083 * 2;
574 /* Compress a black image */
575 auto image = black_image ();
576 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
577 BOOST_REQUIRE (frame.size() < too_big);
579 /* Place it in a bigger block with some zero padding at the end */
580 dcp::ArrayData oversized_frame(too_big);
581 memcpy (oversized_frame.data(), frame.data(), frame.size());
582 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
584 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
585 prepare_directory (dir);
586 auto cpl = dcp_from_frame (oversized_frame, dir);
588 check_verify_result (
591 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
592 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
593 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
598 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
600 int const nearly_too_big = 1302083 * 0.98;
602 /* Compress a black image */
603 auto image = black_image ();
604 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
605 BOOST_REQUIRE (frame.size() < nearly_too_big);
607 /* Place it in a bigger block with some zero padding at the end */
608 dcp::ArrayData oversized_frame(nearly_too_big);
609 memcpy (oversized_frame.data(), frame.data(), frame.size());
610 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
612 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
613 prepare_directory (dir);
614 auto cpl = dcp_from_frame (oversized_frame, dir);
616 check_verify_result (
619 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
620 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
621 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
626 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
628 /* Compress a black image */
629 auto image = black_image ();
630 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
631 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
633 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
634 prepare_directory (dir);
635 auto cpl = dcp_from_frame (frame, dir);
637 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
641 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
643 path const dir("build/test/verify_valid_interop_subtitles");
644 prepare_directory (dir);
645 copy_file ("test/data/subs1.xml", dir / "subs.xml");
646 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
647 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
648 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
650 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
654 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
656 using namespace boost::filesystem;
658 path const dir("build/test/verify_invalid_interop_subtitles");
659 prepare_directory (dir);
660 copy_file ("test/data/subs1.xml", dir / "subs.xml");
661 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
662 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
663 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
666 Editor e (dir / "subs.xml");
667 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
670 check_verify_result (
673 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
674 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
676 dcp::VerificationNote::Type::ERROR,
677 dcp::VerificationNote::Code::INVALID_XML,
678 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
686 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
688 path const dir("build/test/verify_valid_smpte_subtitles");
689 prepare_directory (dir);
690 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
691 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
692 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(25, 1), 300 * 24, 0);
693 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
695 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
699 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
701 using namespace boost::filesystem;
703 path const dir("build/test/verify_invalid_smpte_subtitles");
704 prepare_directory (dir);
705 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
706 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
707 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
708 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
710 check_verify_result (
713 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
715 dcp::VerificationNote::Type::ERROR,
716 dcp::VerificationNote::Code::INVALID_XML,
717 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
721 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
727 BOOST_AUTO_TEST_CASE (verify_external_asset)
729 path const ov_dir("build/test/verify_external_asset");
730 prepare_directory (ov_dir);
732 auto image = black_image ();
733 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
734 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
735 dcp_from_frame (frame, ov_dir);
737 dcp::DCP ov (ov_dir);
740 path const vf_dir("build/test/verify_external_asset_vf");
741 prepare_directory (vf_dir);
743 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
744 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
746 check_verify_result (
749 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
755 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
757 path const dir("build/test/verify_valid_cpl_metadata");
758 prepare_directory (dir);
760 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
761 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
762 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
764 auto reel = make_shared<dcp::Reel>();
765 reel->add (reel_asset);
767 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
768 reel->add (simple_markers(16 * 24));
770 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
772 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
773 cpl->set_main_sound_sample_rate (48000);
774 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
775 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
776 cpl->set_version_number (1);
781 dcp::Standard::SMPTE,
782 dcp::String::compose("libdcp %1", dcp::version),
783 dcp::String::compose("libdcp %1", dcp::version),
784 dcp::LocalTime().as_string(),
790 path find_cpl (path dir)
792 for (auto i: directory_iterator(dir)) {
793 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
798 BOOST_REQUIRE (false);
803 /* DCP with invalid CompositionMetadataAsset */
804 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
806 using namespace boost::filesystem;
808 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
809 prepare_directory (dir);
811 auto reel = make_shared<dcp::Reel>();
812 reel->add (black_picture_asset(dir));
813 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
815 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
816 cpl->set_main_sound_sample_rate (48000);
817 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
818 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
819 cpl->set_version_number (1);
821 reel->add (simple_markers());
826 dcp::Standard::SMPTE,
827 dcp::String::compose("libdcp %1", dcp::version),
828 dcp::String::compose("libdcp %1", dcp::version),
829 dcp::LocalTime().as_string(),
834 Editor e (find_cpl(dir));
835 e.replace ("MainSound", "MainSoundX");
838 check_verify_result (
841 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
842 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
844 dcp::VerificationNote::Type::ERROR,
845 dcp::VerificationNote::Code::INVALID_XML,
846 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
847 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
848 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
849 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
850 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
851 "ExtensionMetadataList?,)'"),
852 canonical(cpl->file().get()),
855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
860 /* DCP with invalid CompositionMetadataAsset */
861 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
863 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
864 prepare_directory (dir);
866 auto reel = make_shared<dcp::Reel>();
867 reel->add (black_picture_asset(dir));
868 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
870 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
871 cpl->set_main_sound_sample_rate (48000);
872 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
873 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
878 dcp::Standard::SMPTE,
879 dcp::String::compose("libdcp %1", dcp::version),
880 dcp::String::compose("libdcp %1", dcp::version),
881 dcp::LocalTime().as_string(),
886 Editor e (find_cpl(dir));
887 e.replace ("meta:Width", "meta:WidthX");
890 check_verify_result (
892 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
897 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
899 path const dir("build/test/verify_invalid_language1");
900 prepare_directory (dir);
901 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
902 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
903 asset->_language = "wrong-andbad";
904 asset->write (dir / "subs.mxf");
905 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
906 reel_asset->_language = "badlang";
907 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
909 check_verify_result (
912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
919 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
920 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
922 path const dir("build/test/verify_invalid_language2");
923 prepare_directory (dir);
924 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
925 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
926 asset->_language = "wrong-andbad";
927 asset->write (dir / "subs.mxf");
928 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
929 reel_asset->_language = "badlang";
930 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
932 check_verify_result (
935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
942 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
943 * the release territory.
945 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
947 path const dir("build/test/verify_invalid_language3");
948 prepare_directory (dir);
950 auto picture = simple_picture (dir, "foo");
951 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
952 auto reel = make_shared<dcp::Reel>();
953 reel->add (reel_picture);
954 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
955 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
956 reel->add (reel_sound);
957 reel->add (simple_markers());
959 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
961 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
962 cpl->_additional_subtitle_languages.push_back("andso-is-this");
963 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
964 cpl->set_main_sound_sample_rate (48000);
965 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
966 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
967 cpl->set_version_number (1);
968 cpl->_release_territory = "fred-jim";
969 auto dcp = make_shared<dcp::DCP>(dir);
972 dcp::Standard::SMPTE,
973 dcp::String::compose("libdcp %1", dcp::version),
974 dcp::String::compose("libdcp %1", dcp::version),
975 dcp::LocalTime().as_string(),
979 check_verify_result (
982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
991 vector<dcp::VerificationNote>
992 check_picture_size (int width, int height, int frame_rate, bool three_d)
994 using namespace boost::filesystem;
996 path dcp_path = "build/test/verify_picture_test";
997 prepare_directory (dcp_path);
999 shared_ptr<dcp::PictureAsset> mp;
1001 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1003 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1005 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1007 auto image = black_image (dcp::Size(width, height));
1008 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1009 int const length = three_d ? frame_rate * 2 : frame_rate;
1010 for (int i = 0; i < length; ++i) {
1011 picture_writer->write (j2c.data(), j2c.size());
1013 picture_writer->finalize ();
1015 auto d = make_shared<dcp::DCP>(dcp_path);
1016 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1017 cpl->set_annotation_text ("A Test DCP");
1018 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1019 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1020 cpl->set_main_sound_sample_rate (48000);
1021 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1022 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1023 cpl->set_version_number (1);
1025 auto reel = make_shared<dcp::Reel>();
1028 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1030 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1033 reel->add (simple_markers(frame_rate));
1039 dcp::Standard::SMPTE,
1040 dcp::String::compose("libdcp %1", dcp::version),
1041 dcp::String::compose("libdcp %1", dcp::version),
1042 dcp::LocalTime().as_string(),
1046 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1052 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1054 auto notes = check_picture_size(width, height, frame_rate, three_d);
1055 BOOST_CHECK_EQUAL (notes.size(), 0U);
1061 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1063 auto notes = check_picture_size(width, height, frame_rate, three_d);
1064 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1065 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1066 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1072 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1074 auto notes = check_picture_size(width, height, frame_rate, three_d);
1075 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1076 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1077 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1083 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1085 auto notes = check_picture_size(width, height, frame_rate, three_d);
1086 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1087 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1088 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1092 BOOST_AUTO_TEST_CASE (verify_picture_size)
1094 using namespace boost::filesystem;
1097 check_picture_size_ok (2048, 858, 24, false);
1098 check_picture_size_ok (2048, 858, 25, false);
1099 check_picture_size_ok (2048, 858, 48, false);
1100 check_picture_size_ok (2048, 858, 24, true);
1101 check_picture_size_ok (2048, 858, 25, true);
1102 check_picture_size_ok (2048, 858, 48, true);
1105 check_picture_size_ok (1998, 1080, 24, false);
1106 check_picture_size_ok (1998, 1080, 25, false);
1107 check_picture_size_ok (1998, 1080, 48, false);
1108 check_picture_size_ok (1998, 1080, 24, true);
1109 check_picture_size_ok (1998, 1080, 25, true);
1110 check_picture_size_ok (1998, 1080, 48, true);
1113 check_picture_size_ok (4096, 1716, 24, false);
1116 check_picture_size_ok (3996, 2160, 24, false);
1118 /* Bad frame size */
1119 check_picture_size_bad_frame_size (2050, 858, 24, false);
1120 check_picture_size_bad_frame_size (2048, 658, 25, false);
1121 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1122 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1124 /* Bad 2K frame rate */
1125 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1126 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1127 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1129 /* Bad 4K frame rate */
1130 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1131 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1134 auto notes = check_picture_size(3996, 2160, 24, true);
1135 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1136 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1137 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1143 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1146 make_shared<dcp::SubtitleString>(
1154 dcp::Time(start_frame, 24, 24),
1155 dcp::Time(end_frame, 24, 24),
1157 dcp::HAlign::CENTER,
1159 dcp::VAlign::CENTER,
1160 dcp::Direction::LTR,
1171 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1173 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1174 prepare_directory (dir);
1176 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1177 for (int i = 0; i < 2048; ++i) {
1178 add_test_subtitle (asset, i * 24, i * 24 + 20);
1180 asset->set_language (dcp::LanguageTag("de-DE"));
1181 asset->write (dir / "subs.mxf");
1182 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 2049 * 24, 0);
1183 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1185 check_verify_result (
1188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1190 dcp::VerificationNote::Type::BV21_ERROR,
1191 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1193 canonical(dir / "subs.mxf")
1195 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1196 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1202 shared_ptr<dcp::SMPTESubtitleAsset>
1203 make_large_subtitle_asset (path font_file)
1205 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1206 dcp::ArrayData big_fake_font(1024 * 1024);
1207 big_fake_font.write (font_file);
1208 for (int i = 0; i < 116; ++i) {
1209 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1217 verify_timed_text_asset_too_large (string name)
1219 auto const dir = path("build/test") / name;
1220 prepare_directory (dir);
1221 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1222 add_test_subtitle (asset, 0, 20);
1223 asset->set_language (dcp::LanguageTag("de-DE"));
1224 asset->write (dir / "subs.mxf");
1226 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1227 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1229 check_verify_result (
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1233 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1241 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1243 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1244 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1248 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1250 path dir = "build/test/verify_missing_subtitle_language";
1251 prepare_directory (dir);
1252 auto dcp = make_simple (dir, 1, 240);
1255 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1256 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1257 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1258 "<ContentTitleText>Content</ContentTitleText>"
1259 "<AnnotationText>Annotation</AnnotationText>"
1260 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1261 "<ReelNumber>1</ReelNumber>"
1262 "<EditRate>25 1</EditRate>"
1263 "<TimeCodeRate>25</TimeCodeRate>"
1264 "<StartTime>00:00:00:00</StartTime>"
1265 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1267 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1268 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1269 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1275 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1276 BOOST_REQUIRE (xml_file);
1277 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1279 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1280 subs->write (dir / "subs.mxf");
1282 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1283 dcp->cpls().front()->reels().front()->add(reel_subs);
1285 dcp::Standard::SMPTE,
1286 dcp::String::compose("libdcp %1", dcp::version),
1287 dcp::String::compose("libdcp %1", dcp::version),
1288 dcp::LocalTime().as_string(),
1292 check_verify_result (
1295 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1296 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1301 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1303 path path ("build/test/verify_mismatched_subtitle_languages");
1304 auto dcp = make_simple (path, 2, 240);
1305 auto cpl = dcp->cpls()[0];
1308 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1309 subs->set_language (dcp::LanguageTag("de-DE"));
1310 subs->add (simple_subtitle());
1311 subs->write (path / "subs1.mxf");
1312 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1313 cpl->reels()[0]->add(reel_subs);
1317 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1318 subs->set_language (dcp::LanguageTag("en-US"));
1319 subs->add (simple_subtitle());
1320 subs->write (path / "subs2.mxf");
1321 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1322 cpl->reels()[1]->add(reel_subs);
1326 dcp::Standard::SMPTE,
1327 dcp::String::compose("libdcp %1", dcp::version),
1328 dcp::String::compose("libdcp %1", dcp::version),
1329 dcp::LocalTime().as_string(),
1333 check_verify_result (
1336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1343 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1345 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1346 auto dcp = make_simple (path, 2, 240);
1347 auto cpl = dcp->cpls()[0];
1350 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1351 ccaps->set_language (dcp::LanguageTag("de-DE"));
1352 ccaps->add (simple_subtitle());
1353 ccaps->write (path / "subs1.mxf");
1354 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1355 cpl->reels()[0]->add(reel_ccaps);
1359 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1360 ccaps->set_language (dcp::LanguageTag("en-US"));
1361 ccaps->add (simple_subtitle());
1362 ccaps->write (path / "subs2.mxf");
1363 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1364 cpl->reels()[1]->add(reel_ccaps);
1368 dcp::Standard::SMPTE,
1369 dcp::String::compose("libdcp %1", dcp::version),
1370 dcp::String::compose("libdcp %1", dcp::version),
1371 dcp::LocalTime().as_string(),
1375 check_verify_result (
1378 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1379 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1384 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1386 path dir = "build/test/verify_missing_subtitle_start_time";
1387 prepare_directory (dir);
1388 auto dcp = make_simple (dir, 1, 240);
1391 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1392 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1393 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1394 "<ContentTitleText>Content</ContentTitleText>"
1395 "<AnnotationText>Annotation</AnnotationText>"
1396 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1397 "<ReelNumber>1</ReelNumber>"
1398 "<Language>de-DE</Language>"
1399 "<EditRate>25 1</EditRate>"
1400 "<TimeCodeRate>25</TimeCodeRate>"
1401 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1403 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1404 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1405 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1411 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1412 BOOST_REQUIRE (xml_file);
1413 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1415 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1416 subs->write (dir / "subs.mxf");
1418 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1419 dcp->cpls().front()->reels().front()->add(reel_subs);
1421 dcp::Standard::SMPTE,
1422 dcp::String::compose("libdcp %1", dcp::version),
1423 dcp::String::compose("libdcp %1", dcp::version),
1424 dcp::LocalTime().as_string(),
1428 check_verify_result (
1431 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1432 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1437 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1439 path dir = "build/test/verify_invalid_subtitle_start_time";
1440 prepare_directory (dir);
1441 auto dcp = make_simple (dir, 1, 240);
1444 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1445 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1446 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1447 "<ContentTitleText>Content</ContentTitleText>"
1448 "<AnnotationText>Annotation</AnnotationText>"
1449 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1450 "<ReelNumber>1</ReelNumber>"
1451 "<Language>de-DE</Language>"
1452 "<EditRate>25 1</EditRate>"
1453 "<TimeCodeRate>25</TimeCodeRate>"
1454 "<StartTime>00:00:02:00</StartTime>"
1455 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1457 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1458 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1459 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1465 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1466 BOOST_REQUIRE (xml_file);
1467 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1469 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1470 subs->write (dir / "subs.mxf");
1472 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1473 dcp->cpls().front()->reels().front()->add(reel_subs);
1475 dcp::Standard::SMPTE,
1476 dcp::String::compose("libdcp %1", dcp::version),
1477 dcp::String::compose("libdcp %1", dcp::version),
1478 dcp::LocalTime().as_string(),
1482 check_verify_result (
1485 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1486 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1494 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1497 , v_position(v_position_)
1509 shared_ptr<dcp::CPL>
1510 dcp_with_text (path dir, vector<TestText> subs)
1512 prepare_directory (dir);
1513 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1514 asset->set_start_time (dcp::Time());
1515 for (auto i: subs) {
1516 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1518 asset->set_language (dcp::LanguageTag("de-DE"));
1519 asset->write (dir / "subs.mxf");
1521 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1522 return write_dcp_with_single_asset (dir, reel_asset);
1526 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1528 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1529 /* Just too early */
1530 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1531 check_verify_result (
1534 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1535 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1541 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1543 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1544 /* Just late enough */
1545 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1546 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1550 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1552 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1553 prepare_directory (dir);
1555 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1556 asset1->set_start_time (dcp::Time());
1557 /* Just late enough */
1558 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1559 asset1->set_language (dcp::LanguageTag("de-DE"));
1560 asset1->write (dir / "subs1.mxf");
1561 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1562 auto reel1 = make_shared<dcp::Reel>();
1563 reel1->add (reel_asset1);
1564 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1565 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1566 reel1->add (markers1);
1568 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1569 asset2->set_start_time (dcp::Time());
1570 /* This would be too early on first reel but should be OK on the second */
1571 add_test_subtitle (asset2, 0, 4 * 24);
1572 asset2->set_language (dcp::LanguageTag("de-DE"));
1573 asset2->write (dir / "subs2.mxf");
1574 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1575 auto reel2 = make_shared<dcp::Reel>();
1576 reel2->add (reel_asset2);
1577 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1578 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1579 reel2->add (markers2);
1581 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1584 auto dcp = make_shared<dcp::DCP>(dir);
1587 dcp::Standard::SMPTE,
1588 dcp::String::compose("libdcp %1", dcp::version),
1589 dcp::String::compose("libdcp %1", dcp::version),
1590 dcp::LocalTime().as_string(),
1595 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1599 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1601 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1602 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1606 { 5 * 24 + 1, 6 * 24 },
1608 check_verify_result (
1611 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1612 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1617 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1619 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1620 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1624 { 5 * 24 + 16, 8 * 24 },
1626 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1630 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1632 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1633 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1634 check_verify_result (
1637 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1638 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1643 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1645 auto const dir = path("build/test/verify_valid_subtitle_duration");
1646 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1647 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1651 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1653 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1654 prepare_directory (dir);
1655 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1656 asset->set_start_time (dcp::Time());
1657 add_test_subtitle (asset, 0, 4 * 24);
1658 asset->set_language (dcp::LanguageTag("de-DE"));
1659 asset->write (dir / "subs.mxf");
1661 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1662 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1663 check_verify_result (
1666 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1667 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1668 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1674 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1676 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1677 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1680 { 96, 200, 0.0, "We" },
1681 { 96, 200, 0.1, "have" },
1682 { 96, 200, 0.2, "four" },
1683 { 96, 200, 0.3, "lines" }
1685 check_verify_result (
1688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1694 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1696 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1697 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1700 { 96, 200, 0.0, "We" },
1701 { 96, 200, 0.1, "have" },
1702 { 96, 200, 0.2, "four" },
1704 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1708 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1710 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1711 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1714 { 96, 300, 0.0, "We" },
1715 { 96, 300, 0.1, "have" },
1716 { 150, 180, 0.2, "four" },
1717 { 150, 180, 0.3, "lines" }
1719 check_verify_result (
1722 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1728 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1730 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1731 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1734 { 96, 300, 0.0, "We" },
1735 { 96, 300, 0.1, "have" },
1736 { 150, 180, 0.2, "four" },
1737 { 190, 250, 0.3, "lines" }
1739 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1743 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1745 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1746 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1749 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1751 check_verify_result (
1754 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1755 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1760 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1762 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1763 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1766 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1768 check_verify_result (
1771 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1772 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1777 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1779 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1780 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1783 { 96, 200, 0.0, "We" },
1784 { 96, 200, 0.1, "have" },
1785 { 96, 200, 0.2, "four" },
1786 { 96, 200, 0.3, "lines" }
1788 check_verify_result (
1791 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1792 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1797 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1799 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1800 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1803 { 96, 200, 0.0, "We" },
1804 { 96, 200, 0.1, "have" },
1805 { 96, 200, 0.2, "four" },
1807 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1811 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1813 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1814 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1817 { 96, 300, 0.0, "We" },
1818 { 96, 300, 0.1, "have" },
1819 { 150, 180, 0.2, "four" },
1820 { 150, 180, 0.3, "lines" }
1822 check_verify_result (
1825 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1826 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1831 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1833 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1834 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1837 { 96, 300, 0.0, "We" },
1838 { 96, 300, 0.1, "have" },
1839 { 150, 180, 0.2, "four" },
1840 { 190, 250, 0.3, "lines" }
1842 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1846 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1848 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1849 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1852 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1854 check_verify_result (
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1863 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1865 path const dir("build/test/verify_invalid_sound_frame_rate");
1866 prepare_directory (dir);
1868 auto picture = simple_picture (dir, "foo");
1869 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1870 auto reel = make_shared<dcp::Reel>();
1871 reel->add (reel_picture);
1872 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1873 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1874 reel->add (reel_sound);
1875 reel->add (simple_markers());
1876 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1878 auto dcp = make_shared<dcp::DCP>(dir);
1881 dcp::Standard::SMPTE,
1882 dcp::String::compose("libdcp %1", dcp::version),
1883 dcp::String::compose("libdcp %1", dcp::version),
1884 dcp::LocalTime().as_string(),
1888 check_verify_result (
1891 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1892 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1897 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1899 path const dir("build/test/verify_missing_cpl_annotation_text");
1900 auto dcp = make_simple (dir);
1902 dcp::Standard::SMPTE,
1903 dcp::String::compose("libdcp %1", dcp::version),
1904 dcp::String::compose("libdcp %1", dcp::version),
1905 dcp::LocalTime().as_string(),
1909 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1911 auto const cpl = dcp->cpls()[0];
1914 BOOST_REQUIRE (cpl->file());
1915 Editor e(cpl->file().get());
1916 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1919 check_verify_result (
1922 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1923 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1928 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1930 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1931 auto dcp = make_simple (dir);
1933 dcp::Standard::SMPTE,
1934 dcp::String::compose("libdcp %1", dcp::version),
1935 dcp::String::compose("libdcp %1", dcp::version),
1936 dcp::LocalTime().as_string(),
1940 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1941 auto const cpl = dcp->cpls()[0];
1944 BOOST_REQUIRE (cpl->file());
1945 Editor e(cpl->file().get());
1946 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1949 check_verify_result (
1952 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1953 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1958 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1960 path const dir("build/test/verify_mismatched_asset_duration");
1961 prepare_directory (dir);
1962 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1963 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1965 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1966 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1968 auto reel = make_shared<dcp::Reel>(
1969 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1970 make_shared<dcp::ReelSoundAsset>(ms, 0)
1973 reel->add (simple_markers());
1978 dcp::Standard::SMPTE,
1979 dcp::String::compose("libdcp %1", dcp::version),
1980 dcp::String::compose("libdcp %1", dcp::version),
1981 dcp::LocalTime().as_string(),
1985 check_verify_result (
1988 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1989 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1996 shared_ptr<dcp::CPL>
1997 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1999 prepare_directory (dir);
2000 auto dcp = make_shared<dcp::DCP>(dir);
2001 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2003 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2004 subs->set_language (dcp::LanguageTag("de-DE"));
2005 subs->set_start_time (dcp::Time());
2006 subs->add (simple_subtitle());
2007 subs->write (dir / "subs.mxf");
2008 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
2010 auto reel1 = make_shared<dcp::Reel>(
2011 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2012 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2016 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2019 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2020 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2021 reel1->add (markers1);
2025 auto reel2 = make_shared<dcp::Reel>(
2026 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2027 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2031 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2034 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2035 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2036 reel2->add (markers2);
2042 dcp::Standard::SMPTE,
2043 dcp::String::compose("libdcp %1", dcp::version),
2044 dcp::String::compose("libdcp %1", dcp::version),
2045 dcp::LocalTime().as_string(),
2053 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2056 path dir ("build/test/missing_main_subtitle_from_some_reels");
2057 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2058 check_verify_result (
2061 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2062 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2068 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2069 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2070 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2074 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2075 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2076 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2082 shared_ptr<dcp::CPL>
2083 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2085 prepare_directory (dir);
2086 auto dcp = make_shared<dcp::DCP>(dir);
2087 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2089 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2090 subs->set_language (dcp::LanguageTag("de-DE"));
2091 subs->set_start_time (dcp::Time());
2092 subs->add (simple_subtitle());
2093 subs->write (dir / "subs.mxf");
2095 auto reel1 = make_shared<dcp::Reel>(
2096 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2097 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2100 for (int i = 0; i < caps_in_reel1; ++i) {
2101 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2104 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2105 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2106 reel1->add (markers1);
2110 auto reel2 = make_shared<dcp::Reel>(
2111 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2112 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2115 for (int i = 0; i < caps_in_reel2; ++i) {
2116 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2119 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2120 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2121 reel2->add (markers2);
2127 dcp::Standard::SMPTE,
2128 dcp::String::compose("libdcp %1", dcp::version),
2129 dcp::String::compose("libdcp %1", dcp::version),
2130 dcp::LocalTime().as_string(),
2138 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2141 path dir ("build/test/mismatched_closed_caption_asset_counts");
2142 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2143 check_verify_result (
2146 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2147 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2152 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2153 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2154 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2158 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2159 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2160 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2167 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2169 prepare_directory (dir);
2170 auto dcp = make_shared<dcp::DCP>(dir);
2171 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2173 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2174 subs->set_language (dcp::LanguageTag("de-DE"));
2175 subs->set_start_time (dcp::Time());
2176 subs->add (simple_subtitle());
2177 subs->write (dir / "subs.mxf");
2178 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2181 auto reel = make_shared<dcp::Reel>(
2182 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2183 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2186 reel->add (reel_text);
2188 reel->add (simple_markers(240));
2194 dcp::Standard::SMPTE,
2195 dcp::String::compose("libdcp %1", dcp::version),
2196 dcp::String::compose("libdcp %1", dcp::version),
2197 dcp::LocalTime().as_string(),
2201 check_verify_result (
2204 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2205 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2210 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2212 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2213 "build/test/verify_subtitle_entry_point_must_be_present",
2214 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2215 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2216 asset->unset_entry_point ();
2220 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2221 "build/test/verify_subtitle_entry_point_must_be_zero",
2222 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2223 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2224 asset->set_entry_point (4);
2228 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2229 "build/test/verify_closed_caption_entry_point_must_be_present",
2230 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2231 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2232 asset->unset_entry_point ();
2236 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2237 "build/test/verify_closed_caption_entry_point_must_be_zero",
2238 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2239 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2240 asset->set_entry_point (9);
2246 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2250 path const dir("build/test/verify_missing_hash");
2251 auto dcp = make_simple (dir);
2253 dcp::Standard::SMPTE,
2254 dcp::String::compose("libdcp %1", dcp::version),
2255 dcp::String::compose("libdcp %1", dcp::version),
2256 dcp::LocalTime().as_string(),
2260 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2261 auto const cpl = dcp->cpls()[0];
2264 BOOST_REQUIRE (cpl->file());
2265 Editor e(cpl->file().get());
2266 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2269 check_verify_result (
2272 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2273 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2280 verify_markers_test (
2282 vector<pair<dcp::Marker, dcp::Time>> markers,
2283 vector<dcp::VerificationNote> test_notes
2286 auto dcp = make_simple (dir);
2287 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2288 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2289 for (auto const& i: markers) {
2290 markers_asset->set (i.first, i.second);
2292 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2294 dcp::Standard::SMPTE,
2295 dcp::String::compose("libdcp %1", dcp::version),
2296 dcp::String::compose("libdcp %1", dcp::version),
2297 dcp::LocalTime().as_string(),
2301 check_verify_result ({dir}, test_notes);
2305 BOOST_AUTO_TEST_CASE (verify_markers)
2307 verify_markers_test (
2308 "build/test/verify_markers_all_correct",
2310 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2311 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2312 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2313 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2318 verify_markers_test (
2319 "build/test/verify_markers_missing_ffec",
2321 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2322 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2323 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2326 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2329 verify_markers_test (
2330 "build/test/verify_markers_missing_ffmc",
2332 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2333 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2334 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2340 verify_markers_test (
2341 "build/test/verify_markers_missing_ffoc",
2343 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2344 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2345 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2348 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2351 verify_markers_test (
2352 "build/test/verify_markers_missing_lfoc",
2354 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2355 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2356 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2359 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2362 verify_markers_test (
2363 "build/test/verify_markers_incorrect_ffoc",
2365 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2366 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2367 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2368 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2371 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2374 verify_markers_test (
2375 "build/test/verify_markers_incorrect_lfoc",
2377 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2378 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2379 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2380 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2383 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2388 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2390 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2391 prepare_directory (dir);
2392 auto dcp = make_simple (dir);
2393 auto cpl = dcp->cpls()[0];
2394 cpl->unset_version_number();
2396 dcp::Standard::SMPTE,
2397 dcp::String::compose("libdcp %1", dcp::version),
2398 dcp::String::compose("libdcp %1", dcp::version),
2399 dcp::LocalTime().as_string(),
2403 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2407 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2409 path dir = "build/test/verify_missing_extension_metadata1";
2410 auto dcp = make_simple (dir);
2412 dcp::Standard::SMPTE,
2413 dcp::String::compose("libdcp %1", dcp::version),
2414 dcp::String::compose("libdcp %1", dcp::version),
2415 dcp::LocalTime().as_string(),
2419 auto cpl = dcp->cpls()[0];
2422 Editor e (cpl->file().get());
2423 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2426 check_verify_result (
2429 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2430 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2435 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2437 path dir = "build/test/verify_missing_extension_metadata2";
2438 auto dcp = make_simple (dir);
2440 dcp::Standard::SMPTE,
2441 dcp::String::compose("libdcp %1", dcp::version),
2442 dcp::String::compose("libdcp %1", dcp::version),
2443 dcp::LocalTime().as_string(),
2447 auto cpl = dcp->cpls()[0];
2450 Editor e (cpl->file().get());
2451 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2454 check_verify_result (
2457 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2458 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2463 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2465 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2466 auto dcp = make_simple (dir);
2468 dcp::Standard::SMPTE,
2469 dcp::String::compose("libdcp %1", dcp::version),
2470 dcp::String::compose("libdcp %1", dcp::version),
2471 dcp::LocalTime().as_string(),
2475 auto const cpl = dcp->cpls()[0];
2478 Editor e (cpl->file().get());
2479 e.replace ("<meta:Name>A", "<meta:NameX>A");
2480 e.replace ("n</meta:Name>", "n</meta:NameX>");
2483 check_verify_result (
2486 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2487 { 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 },
2488 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2493 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2495 path dir = "build/test/verify_invalid_extension_metadata1";
2496 auto dcp = make_simple (dir);
2498 dcp::Standard::SMPTE,
2499 dcp::String::compose("libdcp %1", dcp::version),
2500 dcp::String::compose("libdcp %1", dcp::version),
2501 dcp::LocalTime().as_string(),
2505 auto cpl = dcp->cpls()[0];
2508 Editor e (cpl->file().get());
2509 e.replace ("Application", "Fred");
2512 check_verify_result (
2515 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2516 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2521 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2523 path dir = "build/test/verify_invalid_extension_metadata2";
2524 auto dcp = make_simple (dir);
2526 dcp::Standard::SMPTE,
2527 dcp::String::compose("libdcp %1", dcp::version),
2528 dcp::String::compose("libdcp %1", dcp::version),
2529 dcp::LocalTime().as_string(),
2533 auto cpl = dcp->cpls()[0];
2536 Editor e (cpl->file().get());
2537 e.replace ("DCP Constraints Profile", "Fred");
2540 check_verify_result (
2543 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2544 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2549 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2551 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2552 auto dcp = make_simple (dir);
2554 dcp::Standard::SMPTE,
2555 dcp::String::compose("libdcp %1", dcp::version),
2556 dcp::String::compose("libdcp %1", dcp::version),
2557 dcp::LocalTime().as_string(),
2561 auto const cpl = dcp->cpls()[0];
2564 Editor e (cpl->file().get());
2565 e.replace ("<meta:Value>", "<meta:ValueX>");
2566 e.replace ("</meta:Value>", "</meta:ValueX>");
2569 check_verify_result (
2572 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2573 { 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 },
2574 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2579 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2581 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2582 auto dcp = make_simple (dir);
2584 dcp::Standard::SMPTE,
2585 dcp::String::compose("libdcp %1", dcp::version),
2586 dcp::String::compose("libdcp %1", dcp::version),
2587 dcp::LocalTime().as_string(),
2591 auto const cpl = dcp->cpls()[0];
2594 Editor e (cpl->file().get());
2595 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2598 check_verify_result (
2601 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2602 { 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() },
2607 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2609 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2610 auto dcp = make_simple (dir);
2612 dcp::Standard::SMPTE,
2613 dcp::String::compose("libdcp %1", dcp::version),
2614 dcp::String::compose("libdcp %1", dcp::version),
2615 dcp::LocalTime().as_string(),
2619 auto const cpl = dcp->cpls()[0];
2622 Editor e (cpl->file().get());
2623 e.replace ("<meta:Property>", "<meta:PropertyX>");
2624 e.replace ("</meta:Property>", "</meta:PropertyX>");
2627 check_verify_result (
2630 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2631 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2632 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2637 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2639 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2640 auto dcp = make_simple (dir);
2642 dcp::Standard::SMPTE,
2643 dcp::String::compose("libdcp %1", dcp::version),
2644 dcp::String::compose("libdcp %1", dcp::version),
2645 dcp::LocalTime().as_string(),
2649 auto const cpl = dcp->cpls()[0];
2652 Editor e (cpl->file().get());
2653 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2654 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2657 check_verify_result (
2660 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2661 { 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 },
2662 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2668 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2670 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2671 prepare_directory (dir);
2672 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2673 copy_file (i.path(), dir / i.path().filename());
2676 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2677 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2681 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2684 check_verify_result (
2687 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2688 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2689 /* It's encrypted so the J2K validity checks will fail */
2690 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
2691 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2693 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2695 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2696 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2701 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2703 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2704 prepare_directory (dir);
2705 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2706 copy_file (i.path(), dir / i.path().filename());
2709 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2710 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2713 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2716 check_verify_result (
2719 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2720 /* It's encrypted so the J2K validity checks will fail */
2721 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
2722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2724 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2725 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2727 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2732 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2734 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2735 prepare_directory (dir);
2736 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2737 copy_file (i.path(), dir / i.path().filename());
2741 Editor e (dir / dcp_test1_pkl);
2742 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2745 check_verify_result ({dir}, {});
2749 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2751 path dir ("build/test/verify_must_not_be_partially_encrypted");
2752 prepare_directory (dir);
2756 auto signer = make_shared<dcp::CertificateChain>();
2757 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2758 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2759 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2760 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2762 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2766 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2769 auto writer = mp->start_write (dir / "video.mxf", false);
2770 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2771 for (int i = 0; i < 24; ++i) {
2772 writer->write (j2c.data(), j2c.size());
2774 writer->finalize ();
2776 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2778 auto reel = make_shared<dcp::Reel>(
2779 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2780 make_shared<dcp::ReelSoundAsset>(ms, 0)
2783 reel->add (simple_markers());
2787 cpl->set_content_version (
2788 {"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"}
2790 cpl->set_annotation_text ("A Test DCP");
2791 cpl->set_issuer ("OpenDCP 0.0.25");
2792 cpl->set_creator ("OpenDCP 0.0.25");
2793 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2794 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2795 cpl->set_main_sound_sample_rate (48000);
2796 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2797 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2798 cpl->set_version_number (1);
2802 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);
2804 check_verify_result (
2807 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2808 /* It's encrypted so the J2K validity checks will fail */
2809 {dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")}
2814 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2816 vector<dcp::VerificationNote> notes;
2817 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"));
2818 auto reader = picture.start_read ();
2819 auto frame = reader->get_frame (0);
2820 verify_j2k (frame, notes);
2821 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2825 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2827 vector<dcp::VerificationNote> notes;
2828 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2829 auto reader = picture.start_read ();
2830 auto frame = reader->get_frame (0);
2831 verify_j2k (frame, notes);
2832 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2836 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2838 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2839 prepare_directory (dir);
2840 auto dcp = make_simple (dir);
2841 dcp->write_xml (dcp::Standard::SMPTE);
2842 vector<dcp::VerificationNote> notes;
2843 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2844 auto reader = picture.start_read ();
2845 auto frame = reader->get_frame (0);
2846 verify_j2k (frame, notes);
2847 BOOST_REQUIRE_EQUAL (notes.size(), 0U);