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;
197 dump_notes (vector<dcp::VerificationNote> const & notes)
199 for (auto i: notes) {
200 std::cout << dcp::note_to_string(i) << "\n";
208 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
210 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
211 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
212 for (auto i = 0U; i < notes.size(); ++i) {
213 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
220 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
222 auto dir = setup (1, suffix);
225 Editor e (file(suffix));
226 e.replace (from, to);
229 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
231 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
232 auto i = notes.begin();
233 auto j = codes.begin();
234 while (i != notes.end()) {
235 BOOST_CHECK_EQUAL (i->code(), *j);
242 BOOST_AUTO_TEST_CASE (verify_no_error)
245 auto dir = setup (1, "no_error");
246 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
248 path const cpl_file = dir / dcp_test1_cpl;
249 path const pkl_file = dir / dcp_test1_pkl;
250 path const assetmap_file = dir / "ASSETMAP.xml";
252 auto st = stages.begin();
253 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
254 BOOST_REQUIRE (st->second);
255 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
257 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
258 BOOST_REQUIRE (st->second);
259 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
261 BOOST_CHECK_EQUAL (st->first, "Checking reel");
262 BOOST_REQUIRE (!st->second);
264 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
265 BOOST_REQUIRE (st->second);
266 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
268 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
269 BOOST_REQUIRE (st->second);
270 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
272 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
273 BOOST_REQUIRE (st->second);
274 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
276 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
277 BOOST_REQUIRE (st->second);
278 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
280 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
281 BOOST_REQUIRE (st->second);
282 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
284 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
285 BOOST_REQUIRE (st->second);
286 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
288 BOOST_REQUIRE (st == stages.end());
290 BOOST_CHECK_EQUAL (notes.size(), 0);
294 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
296 using namespace boost::filesystem;
298 auto dir = setup (1, "incorrect_picture_sound_hash");
300 auto video_path = path(dir / "video.mxf");
301 auto mod = fopen(video_path.string().c_str(), "r+b");
303 fseek (mod, 4096, SEEK_SET);
305 fwrite (&x, sizeof(x), 1, mod);
308 auto audio_path = path(dir / "audio.mxf");
309 mod = fopen(audio_path.string().c_str(), "r+b");
311 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
312 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
315 dcp::ASDCPErrorSuspender sus;
316 check_verify_result (
319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
320 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
325 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
327 using namespace boost::filesystem;
329 auto dir = setup (1, "mismatched_picture_sound_hashes");
332 Editor e (dir / dcp_test1_pkl);
333 e.replace ("<Hash>", "<Hash>x");
336 check_verify_result (
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
344 { 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 }
349 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
351 auto dir = setup (1, "failed_read_content_kind");
354 Editor e (dir / dcp_test1_cpl);
355 e.replace ("<ContentKind>", "<ContentKind>x");
358 check_verify_result (
360 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
369 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
377 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
383 asset_map (string suffix)
385 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
389 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
391 check_verify_result_after_replace (
392 "invalid_picture_frame_rate", &cpl,
393 "<FrameRate>24 1", "<FrameRate>99 1",
394 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
395 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
399 BOOST_AUTO_TEST_CASE (verify_missing_asset)
401 auto dir = setup (1, "missing_asset");
402 remove (dir / "video.mxf");
403 check_verify_result (
406 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
411 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
413 check_verify_result_after_replace (
414 "empty_asset_path", &asset_map,
415 "<Path>video.mxf</Path>", "<Path></Path>",
416 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
421 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
423 check_verify_result_after_replace (
424 "mismatched_standard", &cpl,
425 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
426 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
427 dcp::VerificationNote::Code::INVALID_XML,
428 dcp::VerificationNote::Code::INVALID_XML,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
437 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
439 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
440 check_verify_result_after_replace (
441 "invalid_xml_cpl_id", &cpl,
442 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
443 { dcp::VerificationNote::Code::INVALID_XML }
448 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
450 check_verify_result_after_replace (
451 "invalid_xml_issue_date", &cpl,
452 "<IssueDate>", "<IssueDate>x",
453 { dcp::VerificationNote::Code::INVALID_XML,
454 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
459 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
461 check_verify_result_after_replace (
462 "invalid_xml_pkl_id", &pkl,
463 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
464 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
465 { dcp::VerificationNote::Code::INVALID_XML }
470 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
472 check_verify_result_after_replace (
473 "invalix_xml_asset_map_id", &asset_map,
474 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
475 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
476 { dcp::VerificationNote::Code::INVALID_XML }
481 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
484 auto dir = setup (3, "verify_invalid_standard");
485 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
487 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
488 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
489 path const assetmap_file = dir / "ASSETMAP";
491 auto st = stages.begin();
492 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
493 BOOST_REQUIRE (st->second);
494 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
496 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
497 BOOST_REQUIRE (st->second);
498 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
500 BOOST_CHECK_EQUAL (st->first, "Checking reel");
501 BOOST_REQUIRE (!st->second);
503 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
504 BOOST_REQUIRE (st->second);
505 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
507 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
508 BOOST_REQUIRE (st->second);
509 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
511 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
512 BOOST_REQUIRE (st->second);
513 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
515 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
516 BOOST_REQUIRE (st->second);
517 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
519 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
520 BOOST_REQUIRE (st->second);
521 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
523 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
524 BOOST_REQUIRE (st->second);
525 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
527 BOOST_REQUIRE (st == stages.end());
529 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
530 auto i = notes.begin ();
531 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
532 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
534 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
535 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
538 /* DCP with a short asset */
539 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
541 auto dir = setup (8, "invalid_duration");
542 check_verify_result (
545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
546 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
547 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
548 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
549 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
550 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
557 dcp_from_frame (dcp::ArrayData const& frame, path dir)
559 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
560 create_directories (dir);
561 auto writer = asset->start_write (dir / "pic.mxf", true);
562 for (int i = 0; i < 24; ++i) {
563 writer->write (frame.data(), frame.size());
567 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
568 return write_dcp_with_single_asset (dir, reel_asset);
572 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
574 int const too_big = 1302083 * 2;
576 /* Compress a black image */
577 auto image = black_image ();
578 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
579 BOOST_REQUIRE (frame.size() < too_big);
581 /* Place it in a bigger block with some zero padding at the end */
582 dcp::ArrayData oversized_frame(too_big);
583 memcpy (oversized_frame.data(), frame.data(), frame.size());
584 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
586 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
587 prepare_directory (dir);
588 auto cpl = dcp_from_frame (oversized_frame, dir);
590 check_verify_result (
593 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
594 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
595 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
600 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
602 int const nearly_too_big = 1302083 * 0.98;
604 /* Compress a black image */
605 auto image = black_image ();
606 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
607 BOOST_REQUIRE (frame.size() < nearly_too_big);
609 /* Place it in a bigger block with some zero padding at the end */
610 dcp::ArrayData oversized_frame(nearly_too_big);
611 memcpy (oversized_frame.data(), frame.data(), frame.size());
612 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
614 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
615 prepare_directory (dir);
616 auto cpl = dcp_from_frame (oversized_frame, dir);
618 check_verify_result (
621 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
622 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
623 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
628 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
630 /* Compress a black image */
631 auto image = black_image ();
632 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
633 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
635 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
636 prepare_directory (dir);
637 auto cpl = dcp_from_frame (frame, dir);
639 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
643 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
645 path const dir("build/test/verify_valid_interop_subtitles");
646 prepare_directory (dir);
647 copy_file ("test/data/subs1.xml", dir / "subs.xml");
648 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
649 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
650 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
652 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
656 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
658 using namespace boost::filesystem;
660 path const dir("build/test/verify_invalid_interop_subtitles");
661 prepare_directory (dir);
662 copy_file ("test/data/subs1.xml", dir / "subs.xml");
663 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
664 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
665 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
668 Editor e (dir / "subs.xml");
669 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
672 check_verify_result (
675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
676 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
678 dcp::VerificationNote::Type::ERROR,
679 dcp::VerificationNote::Code::INVALID_XML,
680 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
688 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
690 path const dir("build/test/verify_valid_smpte_subtitles");
691 prepare_directory (dir);
692 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
693 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
694 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(25, 1), 300 * 24, 0);
695 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
697 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
701 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
703 using namespace boost::filesystem;
705 path const dir("build/test/verify_invalid_smpte_subtitles");
706 prepare_directory (dir);
707 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
708 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
709 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
710 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
712 check_verify_result (
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
717 dcp::VerificationNote::Type::ERROR,
718 dcp::VerificationNote::Code::INVALID_XML,
719 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
724 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
729 BOOST_AUTO_TEST_CASE (verify_external_asset)
731 path const ov_dir("build/test/verify_external_asset");
732 prepare_directory (ov_dir);
734 auto image = black_image ();
735 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
736 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
737 dcp_from_frame (frame, ov_dir);
739 dcp::DCP ov (ov_dir);
742 path const vf_dir("build/test/verify_external_asset_vf");
743 prepare_directory (vf_dir);
745 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
746 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
748 check_verify_result (
751 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
752 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
757 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
759 path const dir("build/test/verify_valid_cpl_metadata");
760 prepare_directory (dir);
762 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
763 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
764 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
766 auto reel = make_shared<dcp::Reel>();
767 reel->add (reel_asset);
769 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
770 reel->add (simple_markers(16 * 24));
772 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
774 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
775 cpl->set_main_sound_sample_rate (48000);
776 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
777 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
778 cpl->set_version_number (1);
783 dcp::Standard::SMPTE,
784 dcp::String::compose("libdcp %1", dcp::version),
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::LocalTime().as_string(),
792 path find_cpl (path dir)
794 for (auto i: directory_iterator(dir)) {
795 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
800 BOOST_REQUIRE (false);
805 /* DCP with invalid CompositionMetadataAsset */
806 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
808 using namespace boost::filesystem;
810 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
811 prepare_directory (dir);
813 auto reel = make_shared<dcp::Reel>();
814 reel->add (black_picture_asset(dir));
815 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
817 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
818 cpl->set_main_sound_sample_rate (48000);
819 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
820 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
821 cpl->set_version_number (1);
823 reel->add (simple_markers());
828 dcp::Standard::SMPTE,
829 dcp::String::compose("libdcp %1", dcp::version),
830 dcp::String::compose("libdcp %1", dcp::version),
831 dcp::LocalTime().as_string(),
836 Editor e (find_cpl(dir));
837 e.replace ("MainSound", "MainSoundX");
840 check_verify_result (
843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
846 dcp::VerificationNote::Type::ERROR,
847 dcp::VerificationNote::Code::INVALID_XML,
848 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
849 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
850 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
851 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
852 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
853 "ExtensionMetadataList?,)'"),
854 canonical(cpl->file().get()),
857 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
862 /* DCP with invalid CompositionMetadataAsset */
863 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
865 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
866 prepare_directory (dir);
868 auto reel = make_shared<dcp::Reel>();
869 reel->add (black_picture_asset(dir));
870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
872 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873 cpl->set_main_sound_sample_rate (48000);
874 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
880 dcp::Standard::SMPTE,
881 dcp::String::compose("libdcp %1", dcp::version),
882 dcp::String::compose("libdcp %1", dcp::version),
883 dcp::LocalTime().as_string(),
888 Editor e (find_cpl(dir));
889 e.replace ("meta:Width", "meta:WidthX");
892 check_verify_result (
894 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
899 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
901 path const dir("build/test/verify_invalid_language1");
902 prepare_directory (dir);
903 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
904 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
905 asset->_language = "wrong-andbad";
906 asset->write (dir / "subs.mxf");
907 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
908 reel_asset->_language = "badlang";
909 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
911 check_verify_result (
914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
921 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
922 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
924 path const dir("build/test/verify_invalid_language2");
925 prepare_directory (dir);
926 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
927 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
928 asset->_language = "wrong-andbad";
929 asset->write (dir / "subs.mxf");
930 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 300 * 24, 0);
931 reel_asset->_language = "badlang";
932 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
934 check_verify_result (
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
944 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
945 * the release territory.
947 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
949 path const dir("build/test/verify_invalid_language3");
950 prepare_directory (dir);
952 auto picture = simple_picture (dir, "foo");
953 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
954 auto reel = make_shared<dcp::Reel>();
955 reel->add (reel_picture);
956 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
957 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
958 reel->add (reel_sound);
959 reel->add (simple_markers());
961 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
963 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
964 cpl->_additional_subtitle_languages.push_back("andso-is-this");
965 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
966 cpl->set_main_sound_sample_rate (48000);
967 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
968 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
969 cpl->set_version_number (1);
970 cpl->_release_territory = "fred-jim";
971 auto dcp = make_shared<dcp::DCP>(dir);
974 dcp::Standard::SMPTE,
975 dcp::String::compose("libdcp %1", dcp::version),
976 dcp::String::compose("libdcp %1", dcp::version),
977 dcp::LocalTime().as_string(),
981 check_verify_result (
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
993 vector<dcp::VerificationNote>
994 check_picture_size (int width, int height, int frame_rate, bool three_d)
996 using namespace boost::filesystem;
998 path dcp_path = "build/test/verify_picture_test";
999 prepare_directory (dcp_path);
1001 shared_ptr<dcp::PictureAsset> mp;
1003 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1005 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1007 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1009 auto image = black_image (dcp::Size(width, height));
1010 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1011 int const length = three_d ? frame_rate * 2 : frame_rate;
1012 for (int i = 0; i < length; ++i) {
1013 picture_writer->write (j2c.data(), j2c.size());
1015 picture_writer->finalize ();
1017 auto d = make_shared<dcp::DCP>(dcp_path);
1018 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1019 cpl->set_annotation_text ("A Test DCP");
1020 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1021 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1022 cpl->set_main_sound_sample_rate (48000);
1023 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1024 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1025 cpl->set_version_number (1);
1027 auto reel = make_shared<dcp::Reel>();
1030 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1032 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1035 reel->add (simple_markers(frame_rate));
1041 dcp::Standard::SMPTE,
1042 dcp::String::compose("libdcp %1", dcp::version),
1043 dcp::String::compose("libdcp %1", dcp::version),
1044 dcp::LocalTime().as_string(),
1048 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1054 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1056 auto notes = check_picture_size(width, height, frame_rate, three_d);
1057 BOOST_CHECK_EQUAL (notes.size(), 0U);
1063 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1065 auto notes = check_picture_size(width, height, frame_rate, three_d);
1066 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1067 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1068 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1074 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1076 auto notes = check_picture_size(width, height, frame_rate, three_d);
1077 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1078 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1079 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1085 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1087 auto notes = check_picture_size(width, height, frame_rate, three_d);
1088 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1089 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1090 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1094 BOOST_AUTO_TEST_CASE (verify_picture_size)
1096 using namespace boost::filesystem;
1099 check_picture_size_ok (2048, 858, 24, false);
1100 check_picture_size_ok (2048, 858, 25, false);
1101 check_picture_size_ok (2048, 858, 48, false);
1102 check_picture_size_ok (2048, 858, 24, true);
1103 check_picture_size_ok (2048, 858, 25, true);
1104 check_picture_size_ok (2048, 858, 48, true);
1107 check_picture_size_ok (1998, 1080, 24, false);
1108 check_picture_size_ok (1998, 1080, 25, false);
1109 check_picture_size_ok (1998, 1080, 48, false);
1110 check_picture_size_ok (1998, 1080, 24, true);
1111 check_picture_size_ok (1998, 1080, 25, true);
1112 check_picture_size_ok (1998, 1080, 48, true);
1115 check_picture_size_ok (4096, 1716, 24, false);
1118 check_picture_size_ok (3996, 2160, 24, false);
1120 /* Bad frame size */
1121 check_picture_size_bad_frame_size (2050, 858, 24, false);
1122 check_picture_size_bad_frame_size (2048, 658, 25, false);
1123 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1124 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1126 /* Bad 2K frame rate */
1127 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1128 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1129 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1131 /* Bad 4K frame rate */
1132 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1133 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1136 auto notes = check_picture_size(3996, 2160, 24, true);
1137 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1138 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1139 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1145 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1148 make_shared<dcp::SubtitleString>(
1156 dcp::Time(start_frame, 24, 24),
1157 dcp::Time(end_frame, 24, 24),
1159 dcp::HAlign::CENTER,
1161 dcp::VAlign::CENTER,
1162 dcp::Direction::LTR,
1173 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1175 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1176 prepare_directory (dir);
1178 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1179 for (int i = 0; i < 2048; ++i) {
1180 add_test_subtitle (asset, i * 24, i * 24 + 20);
1182 asset->set_language (dcp::LanguageTag("de-DE"));
1183 asset->write (dir / "subs.mxf");
1184 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 2049 * 24, 0);
1185 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1187 check_verify_result (
1190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1192 dcp::VerificationNote::Type::BV21_ERROR,
1193 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1195 canonical(dir / "subs.mxf")
1197 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1198 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1204 shared_ptr<dcp::SMPTESubtitleAsset>
1205 make_large_subtitle_asset (path font_file)
1207 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1208 dcp::ArrayData big_fake_font(1024 * 1024);
1209 big_fake_font.write (font_file);
1210 for (int i = 0; i < 116; ++i) {
1211 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1219 verify_timed_text_asset_too_large (string name)
1221 auto const dir = path("build/test") / name;
1222 prepare_directory (dir);
1223 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1224 add_test_subtitle (asset, 0, 20);
1225 asset->set_language (dcp::LanguageTag("de-DE"));
1226 asset->write (dir / "subs.mxf");
1228 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1229 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1231 check_verify_result (
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1237 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1238 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1243 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1245 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1246 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1250 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1252 path dir = "build/test/verify_missing_subtitle_language";
1253 prepare_directory (dir);
1254 auto dcp = make_simple (dir, 1, 240);
1257 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1258 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1259 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1260 "<ContentTitleText>Content</ContentTitleText>"
1261 "<AnnotationText>Annotation</AnnotationText>"
1262 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1263 "<ReelNumber>1</ReelNumber>"
1264 "<EditRate>25 1</EditRate>"
1265 "<TimeCodeRate>25</TimeCodeRate>"
1266 "<StartTime>00:00:00:00</StartTime>"
1267 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1269 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1270 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1271 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1277 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1278 BOOST_REQUIRE (xml_file);
1279 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1281 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1282 subs->write (dir / "subs.mxf");
1284 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1285 dcp->cpls().front()->reels().front()->add(reel_subs);
1287 dcp::Standard::SMPTE,
1288 dcp::String::compose("libdcp %1", dcp::version),
1289 dcp::String::compose("libdcp %1", dcp::version),
1290 dcp::LocalTime().as_string(),
1294 check_verify_result (
1297 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1298 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1303 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1305 path path ("build/test/verify_mismatched_subtitle_languages");
1306 auto dcp = make_simple (path, 2, 240);
1307 auto cpl = dcp->cpls()[0];
1310 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1311 subs->set_language (dcp::LanguageTag("de-DE"));
1312 subs->add (simple_subtitle());
1313 subs->write (path / "subs1.mxf");
1314 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1315 cpl->reels()[0]->add(reel_subs);
1319 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1320 subs->set_language (dcp::LanguageTag("en-US"));
1321 subs->add (simple_subtitle());
1322 subs->write (path / "subs2.mxf");
1323 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1324 cpl->reels()[1]->add(reel_subs);
1328 dcp::Standard::SMPTE,
1329 dcp::String::compose("libdcp %1", dcp::version),
1330 dcp::String::compose("libdcp %1", dcp::version),
1331 dcp::LocalTime().as_string(),
1335 check_verify_result (
1338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1339 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1345 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1347 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1348 auto dcp = make_simple (path, 2, 240);
1349 auto cpl = dcp->cpls()[0];
1352 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1353 ccaps->set_language (dcp::LanguageTag("de-DE"));
1354 ccaps->add (simple_subtitle());
1355 ccaps->write (path / "subs1.mxf");
1356 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1357 cpl->reels()[0]->add(reel_ccaps);
1361 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1362 ccaps->set_language (dcp::LanguageTag("en-US"));
1363 ccaps->add (simple_subtitle());
1364 ccaps->write (path / "subs2.mxf");
1365 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1366 cpl->reels()[1]->add(reel_ccaps);
1370 dcp::Standard::SMPTE,
1371 dcp::String::compose("libdcp %1", dcp::version),
1372 dcp::String::compose("libdcp %1", dcp::version),
1373 dcp::LocalTime().as_string(),
1377 check_verify_result (
1380 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1381 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1386 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1388 path dir = "build/test/verify_missing_subtitle_start_time";
1389 prepare_directory (dir);
1390 auto dcp = make_simple (dir, 1, 240);
1393 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1394 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1395 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1396 "<ContentTitleText>Content</ContentTitleText>"
1397 "<AnnotationText>Annotation</AnnotationText>"
1398 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1399 "<ReelNumber>1</ReelNumber>"
1400 "<Language>de-DE</Language>"
1401 "<EditRate>25 1</EditRate>"
1402 "<TimeCodeRate>25</TimeCodeRate>"
1403 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1405 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1406 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1407 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1413 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1414 BOOST_REQUIRE (xml_file);
1415 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1417 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1418 subs->write (dir / "subs.mxf");
1420 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1421 dcp->cpls().front()->reels().front()->add(reel_subs);
1423 dcp::Standard::SMPTE,
1424 dcp::String::compose("libdcp %1", dcp::version),
1425 dcp::String::compose("libdcp %1", dcp::version),
1426 dcp::LocalTime().as_string(),
1430 check_verify_result (
1433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1434 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1439 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1441 path dir = "build/test/verify_invalid_subtitle_start_time";
1442 prepare_directory (dir);
1443 auto dcp = make_simple (dir, 1, 240);
1446 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1447 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1448 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1449 "<ContentTitleText>Content</ContentTitleText>"
1450 "<AnnotationText>Annotation</AnnotationText>"
1451 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1452 "<ReelNumber>1</ReelNumber>"
1453 "<Language>de-DE</Language>"
1454 "<EditRate>25 1</EditRate>"
1455 "<TimeCodeRate>25</TimeCodeRate>"
1456 "<StartTime>00:00:02:00</StartTime>"
1457 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1459 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1460 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1461 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1467 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1468 BOOST_REQUIRE (xml_file);
1469 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1471 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1472 subs->write (dir / "subs.mxf");
1474 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1475 dcp->cpls().front()->reels().front()->add(reel_subs);
1477 dcp::Standard::SMPTE,
1478 dcp::String::compose("libdcp %1", dcp::version),
1479 dcp::String::compose("libdcp %1", dcp::version),
1480 dcp::LocalTime().as_string(),
1484 check_verify_result (
1487 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1488 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1496 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1499 , v_position(v_position_)
1511 shared_ptr<dcp::CPL>
1512 dcp_with_text (path dir, vector<TestText> subs)
1514 prepare_directory (dir);
1515 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1516 asset->set_start_time (dcp::Time());
1517 for (auto i: subs) {
1518 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1520 asset->set_language (dcp::LanguageTag("de-DE"));
1521 asset->write (dir / "subs.mxf");
1523 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1524 return write_dcp_with_single_asset (dir, reel_asset);
1528 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1530 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1531 /* Just too early */
1532 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1533 check_verify_result (
1536 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1537 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1543 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1545 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1546 /* Just late enough */
1547 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1548 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1552 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1554 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1555 prepare_directory (dir);
1557 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1558 asset1->set_start_time (dcp::Time());
1559 /* Just late enough */
1560 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1561 asset1->set_language (dcp::LanguageTag("de-DE"));
1562 asset1->write (dir / "subs1.mxf");
1563 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1564 auto reel1 = make_shared<dcp::Reel>();
1565 reel1->add (reel_asset1);
1566 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1567 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1568 reel1->add (markers1);
1570 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1571 asset2->set_start_time (dcp::Time());
1572 /* This would be too early on first reel but should be OK on the second */
1573 add_test_subtitle (asset2, 0, 4 * 24);
1574 asset2->set_language (dcp::LanguageTag("de-DE"));
1575 asset2->write (dir / "subs2.mxf");
1576 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1577 auto reel2 = make_shared<dcp::Reel>();
1578 reel2->add (reel_asset2);
1579 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1580 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1581 reel2->add (markers2);
1583 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1586 auto dcp = make_shared<dcp::DCP>(dir);
1589 dcp::Standard::SMPTE,
1590 dcp::String::compose("libdcp %1", dcp::version),
1591 dcp::String::compose("libdcp %1", dcp::version),
1592 dcp::LocalTime().as_string(),
1597 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1601 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1603 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1604 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1608 { 5 * 24 + 1, 6 * 24 },
1610 check_verify_result (
1613 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1614 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1619 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1621 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1622 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1626 { 5 * 24 + 16, 8 * 24 },
1628 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1632 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1634 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1635 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1636 check_verify_result (
1639 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1640 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1645 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1647 auto const dir = path("build/test/verify_valid_subtitle_duration");
1648 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1649 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1653 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1655 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1656 prepare_directory (dir);
1657 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1658 asset->set_start_time (dcp::Time());
1659 add_test_subtitle (asset, 0, 4 * 24);
1660 asset->set_language (dcp::LanguageTag("de-DE"));
1661 asset->write (dir / "subs.mxf");
1663 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1664 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1665 check_verify_result (
1668 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1670 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1676 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1678 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1679 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1682 { 96, 200, 0.0, "We" },
1683 { 96, 200, 0.1, "have" },
1684 { 96, 200, 0.2, "four" },
1685 { 96, 200, 0.3, "lines" }
1687 check_verify_result (
1690 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1691 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1696 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1698 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1699 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1702 { 96, 200, 0.0, "We" },
1703 { 96, 200, 0.1, "have" },
1704 { 96, 200, 0.2, "four" },
1706 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1710 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1712 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1713 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1716 { 96, 300, 0.0, "We" },
1717 { 96, 300, 0.1, "have" },
1718 { 150, 180, 0.2, "four" },
1719 { 150, 180, 0.3, "lines" }
1721 check_verify_result (
1724 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1730 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1732 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1733 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1736 { 96, 300, 0.0, "We" },
1737 { 96, 300, 0.1, "have" },
1738 { 150, 180, 0.2, "four" },
1739 { 190, 250, 0.3, "lines" }
1741 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1745 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1747 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1748 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1751 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1753 check_verify_result (
1756 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1762 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1764 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1765 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1768 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1770 check_verify_result (
1773 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1774 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1779 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1781 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1782 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1785 { 96, 200, 0.0, "We" },
1786 { 96, 200, 0.1, "have" },
1787 { 96, 200, 0.2, "four" },
1788 { 96, 200, 0.3, "lines" }
1790 check_verify_result (
1793 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1794 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1799 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1801 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1802 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1805 { 96, 200, 0.0, "We" },
1806 { 96, 200, 0.1, "have" },
1807 { 96, 200, 0.2, "four" },
1809 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1813 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1815 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1816 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1819 { 96, 300, 0.0, "We" },
1820 { 96, 300, 0.1, "have" },
1821 { 150, 180, 0.2, "four" },
1822 { 150, 180, 0.3, "lines" }
1824 check_verify_result (
1827 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1828 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1833 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1835 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1836 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1839 { 96, 300, 0.0, "We" },
1840 { 96, 300, 0.1, "have" },
1841 { 150, 180, 0.2, "four" },
1842 { 190, 250, 0.3, "lines" }
1844 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1848 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1850 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1851 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1854 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1856 check_verify_result (
1859 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1860 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1865 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1867 path const dir("build/test/verify_invalid_sound_frame_rate");
1868 prepare_directory (dir);
1870 auto picture = simple_picture (dir, "foo");
1871 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1872 auto reel = make_shared<dcp::Reel>();
1873 reel->add (reel_picture);
1874 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1875 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1876 reel->add (reel_sound);
1877 reel->add (simple_markers());
1878 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1880 auto dcp = make_shared<dcp::DCP>(dir);
1883 dcp::Standard::SMPTE,
1884 dcp::String::compose("libdcp %1", dcp::version),
1885 dcp::String::compose("libdcp %1", dcp::version),
1886 dcp::LocalTime().as_string(),
1890 check_verify_result (
1893 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1894 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1899 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1901 path const dir("build/test/verify_missing_cpl_annotation_text");
1902 auto dcp = make_simple (dir);
1904 dcp::Standard::SMPTE,
1905 dcp::String::compose("libdcp %1", dcp::version),
1906 dcp::String::compose("libdcp %1", dcp::version),
1907 dcp::LocalTime().as_string(),
1911 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1913 auto const cpl = dcp->cpls()[0];
1916 BOOST_REQUIRE (cpl->file());
1917 Editor e(cpl->file().get());
1918 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1921 check_verify_result (
1924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1925 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1930 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1932 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1933 auto dcp = make_simple (dir);
1935 dcp::Standard::SMPTE,
1936 dcp::String::compose("libdcp %1", dcp::version),
1937 dcp::String::compose("libdcp %1", dcp::version),
1938 dcp::LocalTime().as_string(),
1942 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1943 auto const cpl = dcp->cpls()[0];
1946 BOOST_REQUIRE (cpl->file());
1947 Editor e(cpl->file().get());
1948 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1951 check_verify_result (
1954 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1955 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1960 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1962 path const dir("build/test/verify_mismatched_asset_duration");
1963 prepare_directory (dir);
1964 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1965 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1967 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1968 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1970 auto reel = make_shared<dcp::Reel>(
1971 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1972 make_shared<dcp::ReelSoundAsset>(ms, 0)
1975 reel->add (simple_markers());
1980 dcp::Standard::SMPTE,
1981 dcp::String::compose("libdcp %1", dcp::version),
1982 dcp::String::compose("libdcp %1", dcp::version),
1983 dcp::LocalTime().as_string(),
1987 check_verify_result (
1990 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1991 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1998 shared_ptr<dcp::CPL>
1999 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2001 prepare_directory (dir);
2002 auto dcp = make_shared<dcp::DCP>(dir);
2003 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2005 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2006 subs->set_language (dcp::LanguageTag("de-DE"));
2007 subs->set_start_time (dcp::Time());
2008 subs->add (simple_subtitle());
2009 subs->write (dir / "subs.mxf");
2010 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
2012 auto reel1 = make_shared<dcp::Reel>(
2013 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2014 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2018 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2021 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2022 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2023 reel1->add (markers1);
2027 auto reel2 = make_shared<dcp::Reel>(
2028 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2029 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2033 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2036 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2037 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2038 reel2->add (markers2);
2044 dcp::Standard::SMPTE,
2045 dcp::String::compose("libdcp %1", dcp::version),
2046 dcp::String::compose("libdcp %1", dcp::version),
2047 dcp::LocalTime().as_string(),
2055 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2058 path dir ("build/test/missing_main_subtitle_from_some_reels");
2059 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2060 check_verify_result (
2063 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2064 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2070 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2071 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2072 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2076 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2077 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2078 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2084 shared_ptr<dcp::CPL>
2085 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2087 prepare_directory (dir);
2088 auto dcp = make_shared<dcp::DCP>(dir);
2089 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2091 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2092 subs->set_language (dcp::LanguageTag("de-DE"));
2093 subs->set_start_time (dcp::Time());
2094 subs->add (simple_subtitle());
2095 subs->write (dir / "subs.mxf");
2097 auto reel1 = make_shared<dcp::Reel>(
2098 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2099 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2102 for (int i = 0; i < caps_in_reel1; ++i) {
2103 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2106 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2107 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2108 reel1->add (markers1);
2112 auto reel2 = make_shared<dcp::Reel>(
2113 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2114 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2117 for (int i = 0; i < caps_in_reel2; ++i) {
2118 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2121 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2122 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2123 reel2->add (markers2);
2129 dcp::Standard::SMPTE,
2130 dcp::String::compose("libdcp %1", dcp::version),
2131 dcp::String::compose("libdcp %1", dcp::version),
2132 dcp::LocalTime().as_string(),
2140 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2143 path dir ("build/test/mismatched_closed_caption_asset_counts");
2144 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2145 check_verify_result (
2148 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2149 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2154 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2155 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2156 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2160 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2161 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2162 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2169 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2171 prepare_directory (dir);
2172 auto dcp = make_shared<dcp::DCP>(dir);
2173 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2175 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2176 subs->set_language (dcp::LanguageTag("de-DE"));
2177 subs->set_start_time (dcp::Time());
2178 subs->add (simple_subtitle());
2179 subs->write (dir / "subs.mxf");
2180 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2183 auto reel = make_shared<dcp::Reel>(
2184 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2185 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2188 reel->add (reel_text);
2190 reel->add (simple_markers(240));
2196 dcp::Standard::SMPTE,
2197 dcp::String::compose("libdcp %1", dcp::version),
2198 dcp::String::compose("libdcp %1", dcp::version),
2199 dcp::LocalTime().as_string(),
2203 check_verify_result (
2206 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2207 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2212 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2214 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2215 "build/test/verify_subtitle_entry_point_must_be_present",
2216 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2217 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2218 asset->unset_entry_point ();
2222 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2223 "build/test/verify_subtitle_entry_point_must_be_zero",
2224 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2225 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2226 asset->set_entry_point (4);
2230 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2231 "build/test/verify_closed_caption_entry_point_must_be_present",
2232 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2233 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2234 asset->unset_entry_point ();
2238 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2239 "build/test/verify_closed_caption_entry_point_must_be_zero",
2240 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2241 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2242 asset->set_entry_point (9);
2248 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2252 path const dir("build/test/verify_missing_hash");
2253 auto dcp = make_simple (dir);
2255 dcp::Standard::SMPTE,
2256 dcp::String::compose("libdcp %1", dcp::version),
2257 dcp::String::compose("libdcp %1", dcp::version),
2258 dcp::LocalTime().as_string(),
2262 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2263 auto const cpl = dcp->cpls()[0];
2266 BOOST_REQUIRE (cpl->file());
2267 Editor e(cpl->file().get());
2268 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2271 check_verify_result (
2274 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2275 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2282 verify_markers_test (
2284 vector<pair<dcp::Marker, dcp::Time>> markers,
2285 vector<dcp::VerificationNote> test_notes
2288 auto dcp = make_simple (dir);
2289 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2290 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2291 for (auto const& i: markers) {
2292 markers_asset->set (i.first, i.second);
2294 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2296 dcp::Standard::SMPTE,
2297 dcp::String::compose("libdcp %1", dcp::version),
2298 dcp::String::compose("libdcp %1", dcp::version),
2299 dcp::LocalTime().as_string(),
2303 check_verify_result ({dir}, test_notes);
2307 BOOST_AUTO_TEST_CASE (verify_markers)
2309 verify_markers_test (
2310 "build/test/verify_markers_all_correct",
2312 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2313 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2314 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2315 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2320 verify_markers_test (
2321 "build/test/verify_markers_missing_ffec",
2323 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2324 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2325 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2331 verify_markers_test (
2332 "build/test/verify_markers_missing_ffmc",
2334 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2335 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2336 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2339 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2342 verify_markers_test (
2343 "build/test/verify_markers_missing_ffoc",
2345 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2346 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2347 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2350 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2353 verify_markers_test (
2354 "build/test/verify_markers_missing_lfoc",
2356 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2357 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2358 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2361 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2364 verify_markers_test (
2365 "build/test/verify_markers_incorrect_ffoc",
2367 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2368 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2369 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2370 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2373 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2376 verify_markers_test (
2377 "build/test/verify_markers_incorrect_lfoc",
2379 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2380 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2381 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2382 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2385 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2390 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2392 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2393 prepare_directory (dir);
2394 auto dcp = make_simple (dir);
2395 auto cpl = dcp->cpls()[0];
2396 cpl->unset_version_number();
2398 dcp::Standard::SMPTE,
2399 dcp::String::compose("libdcp %1", dcp::version),
2400 dcp::String::compose("libdcp %1", dcp::version),
2401 dcp::LocalTime().as_string(),
2405 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2409 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2411 path dir = "build/test/verify_missing_extension_metadata1";
2412 auto dcp = make_simple (dir);
2414 dcp::Standard::SMPTE,
2415 dcp::String::compose("libdcp %1", dcp::version),
2416 dcp::String::compose("libdcp %1", dcp::version),
2417 dcp::LocalTime().as_string(),
2421 auto cpl = dcp->cpls()[0];
2424 Editor e (cpl->file().get());
2425 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2428 check_verify_result (
2431 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2432 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2437 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2439 path dir = "build/test/verify_missing_extension_metadata2";
2440 auto dcp = make_simple (dir);
2442 dcp::Standard::SMPTE,
2443 dcp::String::compose("libdcp %1", dcp::version),
2444 dcp::String::compose("libdcp %1", dcp::version),
2445 dcp::LocalTime().as_string(),
2449 auto cpl = dcp->cpls()[0];
2452 Editor e (cpl->file().get());
2453 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2456 check_verify_result (
2459 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2460 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2465 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2467 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2468 auto dcp = make_simple (dir);
2470 dcp::Standard::SMPTE,
2471 dcp::String::compose("libdcp %1", dcp::version),
2472 dcp::String::compose("libdcp %1", dcp::version),
2473 dcp::LocalTime().as_string(),
2477 auto const cpl = dcp->cpls()[0];
2480 Editor e (cpl->file().get());
2481 e.replace ("<meta:Name>A", "<meta:NameX>A");
2482 e.replace ("n</meta:Name>", "n</meta:NameX>");
2485 check_verify_result (
2488 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2489 { 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 },
2490 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2495 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2497 path dir = "build/test/verify_invalid_extension_metadata1";
2498 auto dcp = make_simple (dir);
2500 dcp::Standard::SMPTE,
2501 dcp::String::compose("libdcp %1", dcp::version),
2502 dcp::String::compose("libdcp %1", dcp::version),
2503 dcp::LocalTime().as_string(),
2507 auto cpl = dcp->cpls()[0];
2510 Editor e (cpl->file().get());
2511 e.replace ("Application", "Fred");
2514 check_verify_result (
2517 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2518 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2523 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2525 path dir = "build/test/verify_invalid_extension_metadata2";
2526 auto dcp = make_simple (dir);
2528 dcp::Standard::SMPTE,
2529 dcp::String::compose("libdcp %1", dcp::version),
2530 dcp::String::compose("libdcp %1", dcp::version),
2531 dcp::LocalTime().as_string(),
2535 auto cpl = dcp->cpls()[0];
2538 Editor e (cpl->file().get());
2539 e.replace ("DCP Constraints Profile", "Fred");
2542 check_verify_result (
2545 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2546 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2551 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2553 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2554 auto dcp = make_simple (dir);
2556 dcp::Standard::SMPTE,
2557 dcp::String::compose("libdcp %1", dcp::version),
2558 dcp::String::compose("libdcp %1", dcp::version),
2559 dcp::LocalTime().as_string(),
2563 auto const cpl = dcp->cpls()[0];
2566 Editor e (cpl->file().get());
2567 e.replace ("<meta:Value>", "<meta:ValueX>");
2568 e.replace ("</meta:Value>", "</meta:ValueX>");
2571 check_verify_result (
2574 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2575 { 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 },
2576 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2581 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2583 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2584 auto dcp = make_simple (dir);
2586 dcp::Standard::SMPTE,
2587 dcp::String::compose("libdcp %1", dcp::version),
2588 dcp::String::compose("libdcp %1", dcp::version),
2589 dcp::LocalTime().as_string(),
2593 auto const cpl = dcp->cpls()[0];
2596 Editor e (cpl->file().get());
2597 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2600 check_verify_result (
2603 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2604 { 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() },
2609 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2611 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2612 auto dcp = make_simple (dir);
2614 dcp::Standard::SMPTE,
2615 dcp::String::compose("libdcp %1", dcp::version),
2616 dcp::String::compose("libdcp %1", dcp::version),
2617 dcp::LocalTime().as_string(),
2621 auto const cpl = dcp->cpls()[0];
2624 Editor e (cpl->file().get());
2625 e.replace ("<meta:Property>", "<meta:PropertyX>");
2626 e.replace ("</meta:Property>", "</meta:PropertyX>");
2629 check_verify_result (
2632 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2633 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2634 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2639 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2641 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2642 auto dcp = make_simple (dir);
2644 dcp::Standard::SMPTE,
2645 dcp::String::compose("libdcp %1", dcp::version),
2646 dcp::String::compose("libdcp %1", dcp::version),
2647 dcp::LocalTime().as_string(),
2651 auto const cpl = dcp->cpls()[0];
2654 Editor e (cpl->file().get());
2655 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2656 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2659 check_verify_result (
2662 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2663 { 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 },
2664 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2670 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2672 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2673 prepare_directory (dir);
2674 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2675 copy_file (i.path(), dir / i.path().filename());
2678 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2679 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2683 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2686 check_verify_result (
2689 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2690 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2691 /* It's encrypted so the J2K validity checks will fail */
2692 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
2693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2694 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2695 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2696 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2697 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2698 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2703 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2705 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2706 prepare_directory (dir);
2707 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2708 copy_file (i.path(), dir / i.path().filename());
2711 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2712 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2715 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2718 check_verify_result (
2721 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2722 /* It's encrypted so the J2K validity checks will fail */
2723 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
2724 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2726 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2727 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2734 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2736 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2737 prepare_directory (dir);
2738 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2739 copy_file (i.path(), dir / i.path().filename());
2743 Editor e (dir / dcp_test1_pkl);
2744 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2747 check_verify_result ({dir}, {});
2751 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2753 path dir ("build/test/verify_must_not_be_partially_encrypted");
2754 prepare_directory (dir);
2758 auto signer = make_shared<dcp::CertificateChain>();
2759 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2760 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2761 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2762 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2764 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2768 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2771 auto writer = mp->start_write (dir / "video.mxf", false);
2772 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2773 for (int i = 0; i < 24; ++i) {
2774 writer->write (j2c.data(), j2c.size());
2776 writer->finalize ();
2778 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2780 auto reel = make_shared<dcp::Reel>(
2781 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2782 make_shared<dcp::ReelSoundAsset>(ms, 0)
2785 reel->add (simple_markers());
2789 cpl->set_content_version (
2790 {"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"}
2792 cpl->set_annotation_text ("A Test DCP");
2793 cpl->set_issuer ("OpenDCP 0.0.25");
2794 cpl->set_creator ("OpenDCP 0.0.25");
2795 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2796 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2797 cpl->set_main_sound_sample_rate (48000);
2798 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2799 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2800 cpl->set_version_number (1);
2804 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);
2806 check_verify_result (
2809 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2810 /* It's encrypted so the J2K validity checks will fail */
2811 {dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte")}
2816 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2818 vector<dcp::VerificationNote> notes;
2819 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"));
2820 auto reader = picture.start_read ();
2821 auto frame = reader->get_frame (0);
2822 verify_j2k (frame, notes);
2823 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2827 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2829 vector<dcp::VerificationNote> notes;
2830 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2831 auto reader = picture.start_read ();
2832 auto frame = reader->get_frame (0);
2833 verify_j2k (frame, notes);
2834 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2838 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2840 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2841 prepare_directory (dir);
2842 auto dcp = make_simple (dir);
2843 dcp->write_xml (dcp::Standard::SMPTE);
2844 vector<dcp::VerificationNote> notes;
2845 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2846 auto reader = picture.start_read ();
2847 auto frame = reader->get_frame (0);
2848 verify_j2k (frame, notes);
2849 BOOST_REQUIRE_EQUAL (notes.size(), 0U);