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_interop_closed_caption_asset.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_closed_caption_asset.h"
51 #include "reel_smpte_subtitle_asset.h"
52 #include "smpte_subtitle_asset.h"
53 #include "stereo_picture_asset.h"
54 #include "stream_operators.h"
58 #include "verify_j2k.h"
59 #include <boost/test/unit_test.hpp>
60 #include <boost/algorithm/string.hpp>
70 using std::make_shared;
71 using boost::optional;
72 using namespace boost::filesystem;
73 using std::shared_ptr;
76 static list<pair<string, optional<path>>> stages;
77 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
78 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
79 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
80 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
81 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
82 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
83 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
86 stage (string s, optional<path> p)
88 stages.push_back (make_pair (s, p));
98 prepare_directory (path path)
100 using namespace boost::filesystem;
102 create_directories (path);
107 setup (int reference_number, string verify_test_suffix)
109 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
110 prepare_directory (dir);
111 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
112 copy_file (i.path(), dir / i.path().filename());
121 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
123 auto reel = make_shared<dcp::Reel>();
124 reel->add (reel_asset);
125 reel->add (simple_markers());
127 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
129 auto dcp = make_shared<dcp::DCP>(dir);
132 dcp::String::compose("libdcp %1", dcp::version),
133 dcp::String::compose("libdcp %1", dcp::version),
134 dcp::LocalTime().as_string(),
142 /** Class that can alter a file by searching and replacing strings within it.
143 * On destruction modifies the file whose name was given to the constructor.
151 _content = dcp::file_to_string (_path);
156 auto f = fopen(_path.string().c_str(), "w");
158 fwrite (_content.c_str(), _content.length(), 1, f);
162 void replace (string a, string b)
164 auto old_content = _content;
165 boost::algorithm::replace_all (_content, a, b);
166 BOOST_REQUIRE (_content != old_content);
169 void delete_lines (string from, string to)
171 vector<string> lines;
172 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
173 bool deleting = false;
174 auto old_content = _content;
176 for (auto i: lines) {
177 if (i.find(from) != string::npos) {
181 _content += i + "\n";
183 if (deleting && i.find(to) != string::npos) {
187 BOOST_REQUIRE (_content != old_content);
192 std::string _content;
196 LIBDCP_DISABLE_WARNINGS
199 dump_notes (vector<dcp::VerificationNote> const & notes)
201 for (auto i: notes) {
202 std::cout << dcp::note_to_string(i) << "\n";
205 LIBDCP_ENABLE_WARNINGS
210 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
212 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
213 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
214 for (auto i = 0U; i < notes.size(); ++i) {
215 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
222 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
224 auto dir = setup (1, suffix);
227 Editor e (file(suffix));
228 e.replace (from, to);
231 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
233 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
234 auto i = notes.begin();
235 auto j = codes.begin();
236 while (i != notes.end()) {
237 BOOST_CHECK_EQUAL (i->code(), *j);
244 BOOST_AUTO_TEST_CASE (verify_no_error)
247 auto dir = setup (1, "no_error");
248 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
250 path const cpl_file = dir / dcp_test1_cpl;
251 path const pkl_file = dir / dcp_test1_pkl;
252 path const assetmap_file = dir / "ASSETMAP.xml";
254 auto st = stages.begin();
255 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
256 BOOST_REQUIRE (st->second);
257 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
259 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
260 BOOST_REQUIRE (st->second);
261 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
263 BOOST_CHECK_EQUAL (st->first, "Checking reel");
264 BOOST_REQUIRE (!st->second);
266 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
267 BOOST_REQUIRE (st->second);
268 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
270 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
271 BOOST_REQUIRE (st->second);
272 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
274 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
275 BOOST_REQUIRE (st->second);
276 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
278 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
279 BOOST_REQUIRE (st->second);
280 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
282 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
283 BOOST_REQUIRE (st->second);
284 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
286 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
287 BOOST_REQUIRE (st->second);
288 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
290 BOOST_REQUIRE (st == stages.end());
292 BOOST_CHECK_EQUAL (notes.size(), 0);
296 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
298 using namespace boost::filesystem;
300 auto dir = setup (1, "incorrect_picture_sound_hash");
302 auto video_path = path(dir / "video.mxf");
303 auto mod = fopen(video_path.string().c_str(), "r+b");
305 fseek (mod, 4096, SEEK_SET);
307 fwrite (&x, sizeof(x), 1, mod);
310 auto audio_path = path(dir / "audio.mxf");
311 mod = fopen(audio_path.string().c_str(), "r+b");
313 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
314 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
317 dcp::ASDCPErrorSuspender sus;
318 check_verify_result (
321 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
322 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
327 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
329 using namespace boost::filesystem;
331 auto dir = setup (1, "mismatched_picture_sound_hashes");
334 Editor e (dir / dcp_test1_pkl);
335 e.replace ("<Hash>", "<Hash>x");
338 check_verify_result (
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
345 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
346 { 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 }
351 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
353 auto dir = setup (1, "failed_read_content_kind");
356 Editor e (dir / dcp_test1_cpl);
357 e.replace ("<ContentKind>", "<ContentKind>x");
360 check_verify_result (
362 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
371 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
379 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
385 asset_map (string suffix)
387 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
391 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
393 check_verify_result_after_replace (
394 "invalid_picture_frame_rate", &cpl,
395 "<FrameRate>24 1", "<FrameRate>99 1",
396 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
397 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
401 BOOST_AUTO_TEST_CASE (verify_missing_asset)
403 auto dir = setup (1, "missing_asset");
404 remove (dir / "video.mxf");
405 check_verify_result (
408 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
413 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
415 check_verify_result_after_replace (
416 "empty_asset_path", &asset_map,
417 "<Path>video.mxf</Path>", "<Path></Path>",
418 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
423 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
425 check_verify_result_after_replace (
426 "mismatched_standard", &cpl,
427 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
428 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::INVALID_XML,
433 dcp::VerificationNote::Code::INVALID_XML,
434 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
439 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
441 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
442 check_verify_result_after_replace (
443 "invalid_xml_cpl_id", &cpl,
444 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
445 { dcp::VerificationNote::Code::INVALID_XML }
450 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
452 check_verify_result_after_replace (
453 "invalid_xml_issue_date", &cpl,
454 "<IssueDate>", "<IssueDate>x",
455 { dcp::VerificationNote::Code::INVALID_XML,
456 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
461 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
463 check_verify_result_after_replace (
464 "invalid_xml_pkl_id", &pkl,
465 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
466 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
467 { dcp::VerificationNote::Code::INVALID_XML }
472 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
474 check_verify_result_after_replace (
475 "invalix_xml_asset_map_id", &asset_map,
476 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
477 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
478 { dcp::VerificationNote::Code::INVALID_XML }
483 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
486 auto dir = setup (3, "verify_invalid_standard");
487 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
489 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
490 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
491 path const assetmap_file = dir / "ASSETMAP";
493 auto st = stages.begin();
494 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
495 BOOST_REQUIRE (st->second);
496 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
498 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
499 BOOST_REQUIRE (st->second);
500 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
502 BOOST_CHECK_EQUAL (st->first, "Checking reel");
503 BOOST_REQUIRE (!st->second);
505 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
506 BOOST_REQUIRE (st->second);
507 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
509 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
510 BOOST_REQUIRE (st->second);
511 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
513 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
514 BOOST_REQUIRE (st->second);
515 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
517 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
518 BOOST_REQUIRE (st->second);
519 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
521 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
522 BOOST_REQUIRE (st->second);
523 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
525 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
526 BOOST_REQUIRE (st->second);
527 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
529 BOOST_REQUIRE (st == stages.end());
531 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
532 auto i = notes.begin ();
533 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
534 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
536 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
537 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
540 /* DCP with a short asset */
541 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
543 auto dir = setup (8, "invalid_duration");
544 check_verify_result (
547 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
548 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
549 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
550 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
551 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
552 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
559 dcp_from_frame (dcp::ArrayData const& frame, path dir)
561 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
562 create_directories (dir);
563 auto writer = asset->start_write (dir / "pic.mxf", true);
564 for (int i = 0; i < 24; ++i) {
565 writer->write (frame.data(), frame.size());
569 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
570 return write_dcp_with_single_asset (dir, reel_asset);
574 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
576 int const too_big = 1302083 * 2;
578 /* Compress a black image */
579 auto image = black_image ();
580 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
581 BOOST_REQUIRE (frame.size() < too_big);
583 /* Place it in a bigger block with some zero padding at the end */
584 dcp::ArrayData oversized_frame(too_big);
585 memcpy (oversized_frame.data(), frame.data(), frame.size());
586 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
588 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
589 prepare_directory (dir);
590 auto cpl = dcp_from_frame (oversized_frame, dir);
592 check_verify_result (
595 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
596 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
597 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
602 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
604 int const nearly_too_big = 1302083 * 0.98;
606 /* Compress a black image */
607 auto image = black_image ();
608 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
609 BOOST_REQUIRE (frame.size() < nearly_too_big);
611 /* Place it in a bigger block with some zero padding at the end */
612 dcp::ArrayData oversized_frame(nearly_too_big);
613 memcpy (oversized_frame.data(), frame.data(), frame.size());
614 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
616 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
617 prepare_directory (dir);
618 auto cpl = dcp_from_frame (oversized_frame, dir);
620 check_verify_result (
623 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
624 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
625 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
630 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
632 /* Compress a black image */
633 auto image = black_image ();
634 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
635 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
637 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
638 prepare_directory (dir);
639 auto cpl = dcp_from_frame (frame, dir);
641 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
645 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
647 path const dir("build/test/verify_valid_interop_subtitles");
648 prepare_directory (dir);
649 copy_file ("test/data/subs1.xml", dir / "subs.xml");
650 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
651 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
652 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
654 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
658 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
660 using namespace boost::filesystem;
662 path const dir("build/test/verify_invalid_interop_subtitles");
663 prepare_directory (dir);
664 copy_file ("test/data/subs1.xml", dir / "subs.xml");
665 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
666 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
667 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
670 Editor e (dir / "subs.xml");
671 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
674 check_verify_result (
677 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
678 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
680 dcp::VerificationNote::Type::ERROR,
681 dcp::VerificationNote::Code::INVALID_XML,
682 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
690 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
692 path const dir("build/test/verify_valid_smpte_subtitles");
693 prepare_directory (dir);
694 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
695 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
696 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
697 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
699 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
703 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
705 using namespace boost::filesystem;
707 path const dir("build/test/verify_invalid_smpte_subtitles");
708 prepare_directory (dir);
709 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
710 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
711 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
712 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
713 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
715 check_verify_result (
718 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
720 dcp::VerificationNote::Type::ERROR,
721 dcp::VerificationNote::Code::INVALID_XML,
722 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
727 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
732 BOOST_AUTO_TEST_CASE (verify_external_asset)
734 path const ov_dir("build/test/verify_external_asset");
735 prepare_directory (ov_dir);
737 auto image = black_image ();
738 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
739 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
740 dcp_from_frame (frame, ov_dir);
742 dcp::DCP ov (ov_dir);
745 path const vf_dir("build/test/verify_external_asset_vf");
746 prepare_directory (vf_dir);
748 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
749 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
751 check_verify_result (
754 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
755 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
760 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
762 path const dir("build/test/verify_valid_cpl_metadata");
763 prepare_directory (dir);
765 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
766 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
767 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
769 auto reel = make_shared<dcp::Reel>();
770 reel->add (reel_asset);
772 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
773 reel->add (simple_markers(16 * 24));
775 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
777 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
778 cpl->set_main_sound_sample_rate (48000);
779 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
780 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
781 cpl->set_version_number (1);
786 dcp::String::compose("libdcp %1", dcp::version),
787 dcp::String::compose("libdcp %1", dcp::version),
788 dcp::LocalTime().as_string(),
794 path find_cpl (path dir)
796 for (auto i: directory_iterator(dir)) {
797 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
802 BOOST_REQUIRE (false);
807 /* DCP with invalid CompositionMetadataAsset */
808 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
810 using namespace boost::filesystem;
812 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
813 prepare_directory (dir);
815 auto reel = make_shared<dcp::Reel>();
816 reel->add (black_picture_asset(dir));
817 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
819 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
820 cpl->set_main_sound_sample_rate (48000);
821 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
822 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
823 cpl->set_version_number (1);
825 reel->add (simple_markers());
830 dcp::String::compose("libdcp %1", dcp::version),
831 dcp::String::compose("libdcp %1", dcp::version),
832 dcp::LocalTime().as_string(),
837 Editor e (find_cpl(dir));
838 e.replace ("MainSound", "MainSoundX");
841 check_verify_result (
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
847 dcp::VerificationNote::Type::ERROR,
848 dcp::VerificationNote::Code::INVALID_XML,
849 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
850 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
851 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
852 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
853 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
854 "ExtensionMetadataList?,)'"),
855 canonical(cpl->file().get()),
858 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
863 /* DCP with invalid CompositionMetadataAsset */
864 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
866 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
867 prepare_directory (dir);
869 auto reel = make_shared<dcp::Reel>();
870 reel->add (black_picture_asset(dir));
871 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
873 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
874 cpl->set_main_sound_sample_rate (48000);
875 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
876 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
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::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 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::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 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, dcp::Standard::SMPTE);
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::String::compose("libdcp %1", dcp::version),
975 dcp::String::compose("libdcp %1", dcp::version),
976 dcp::LocalTime().as_string(),
980 check_verify_result (
983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
992 vector<dcp::VerificationNote>
993 check_picture_size (int width, int height, int frame_rate, bool three_d)
995 using namespace boost::filesystem;
997 path dcp_path = "build/test/verify_picture_test";
998 prepare_directory (dcp_path);
1000 shared_ptr<dcp::PictureAsset> mp;
1002 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1004 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1006 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1008 auto image = black_image (dcp::Size(width, height));
1009 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1010 int const length = three_d ? frame_rate * 2 : frame_rate;
1011 for (int i = 0; i < length; ++i) {
1012 picture_writer->write (j2c.data(), j2c.size());
1014 picture_writer->finalize ();
1016 auto d = make_shared<dcp::DCP>(dcp_path);
1017 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1018 cpl->set_annotation_text ("A Test DCP");
1019 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1020 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1021 cpl->set_main_sound_sample_rate (48000);
1022 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1023 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1024 cpl->set_version_number (1);
1026 auto reel = make_shared<dcp::Reel>();
1029 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1031 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1034 reel->add (simple_markers(frame_rate));
1040 dcp::String::compose("libdcp %1", dcp::version),
1041 dcp::String::compose("libdcp %1", dcp::version),
1042 dcp::LocalTime().as_string(),
1046 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1052 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1054 auto notes = check_picture_size(width, height, frame_rate, three_d);
1055 BOOST_CHECK_EQUAL (notes.size(), 0U);
1061 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1063 auto notes = check_picture_size(width, height, frame_rate, three_d);
1064 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1065 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1066 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1072 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1074 auto notes = check_picture_size(width, height, frame_rate, three_d);
1075 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1076 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1077 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1083 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1085 auto notes = check_picture_size(width, height, frame_rate, three_d);
1086 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1087 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1088 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1092 BOOST_AUTO_TEST_CASE (verify_picture_size)
1094 using namespace boost::filesystem;
1097 check_picture_size_ok (2048, 858, 24, false);
1098 check_picture_size_ok (2048, 858, 25, false);
1099 check_picture_size_ok (2048, 858, 48, false);
1100 check_picture_size_ok (2048, 858, 24, true);
1101 check_picture_size_ok (2048, 858, 25, true);
1102 check_picture_size_ok (2048, 858, 48, true);
1105 check_picture_size_ok (1998, 1080, 24, false);
1106 check_picture_size_ok (1998, 1080, 25, false);
1107 check_picture_size_ok (1998, 1080, 48, false);
1108 check_picture_size_ok (1998, 1080, 24, true);
1109 check_picture_size_ok (1998, 1080, 25, true);
1110 check_picture_size_ok (1998, 1080, 48, true);
1113 check_picture_size_ok (4096, 1716, 24, false);
1116 check_picture_size_ok (3996, 2160, 24, false);
1118 /* Bad frame size */
1119 check_picture_size_bad_frame_size (2050, 858, 24, false);
1120 check_picture_size_bad_frame_size (2048, 658, 25, false);
1121 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1122 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1124 /* Bad 2K frame rate */
1125 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1126 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1127 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1129 /* Bad 4K frame rate */
1130 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1131 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1134 auto notes = check_picture_size(3996, 2160, 24, true);
1135 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1136 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1137 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1143 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1146 make_shared<dcp::SubtitleString>(
1154 dcp::Time(start_frame, 24, 24),
1155 dcp::Time(end_frame, 24, 24),
1157 dcp::HAlign::CENTER,
1159 dcp::VAlign::CENTER,
1160 dcp::Direction::LTR,
1171 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1173 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1174 prepare_directory (dir);
1176 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1177 for (int i = 0; i < 2048; ++i) {
1178 add_test_subtitle (asset, i * 24, i * 24 + 20);
1180 asset->set_language (dcp::LanguageTag("de-DE"));
1181 asset->write (dir / "subs.mxf");
1182 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1183 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1185 check_verify_result (
1188 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1190 dcp::VerificationNote::Type::BV21_ERROR,
1191 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1193 canonical(dir / "subs.mxf")
1195 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1196 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1202 shared_ptr<dcp::SMPTESubtitleAsset>
1203 make_large_subtitle_asset (path font_file)
1205 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1206 dcp::ArrayData big_fake_font(1024 * 1024);
1207 big_fake_font.write (font_file);
1208 for (int i = 0; i < 116; ++i) {
1209 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1217 verify_timed_text_asset_too_large (string name)
1219 auto const dir = path("build/test") / name;
1220 prepare_directory (dir);
1221 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1222 add_test_subtitle (asset, 0, 240);
1223 asset->set_language (dcp::LanguageTag("de-DE"));
1224 asset->write (dir / "subs.mxf");
1226 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1227 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1229 check_verify_result (
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1233 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1241 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1243 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1244 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1248 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1250 path dir = "build/test/verify_missing_subtitle_language";
1251 prepare_directory (dir);
1252 auto dcp = make_simple (dir, 1, 106);
1255 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1256 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1257 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1258 "<ContentTitleText>Content</ContentTitleText>"
1259 "<AnnotationText>Annotation</AnnotationText>"
1260 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1261 "<ReelNumber>1</ReelNumber>"
1262 "<EditRate>24 1</EditRate>"
1263 "<TimeCodeRate>24</TimeCodeRate>"
1264 "<StartTime>00:00:00:00</StartTime>"
1265 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1267 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1268 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1269 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1275 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1276 BOOST_REQUIRE (xml_file);
1277 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1279 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1280 subs->write (dir / "subs.mxf");
1282 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1283 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1285 dcp::String::compose("libdcp %1", dcp::version),
1286 dcp::String::compose("libdcp %1", dcp::version),
1287 dcp::LocalTime().as_string(),
1291 check_verify_result (
1294 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1295 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1300 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1302 path path ("build/test/verify_mismatched_subtitle_languages");
1303 auto constexpr reel_length = 192;
1304 auto dcp = make_simple (path, 2, reel_length);
1305 auto cpl = dcp->cpls()[0];
1308 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1309 subs->set_language (dcp::LanguageTag("de-DE"));
1310 subs->add (simple_subtitle());
1311 subs->write (path / "subs1.mxf");
1312 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1313 cpl->reels()[0]->add(reel_subs);
1317 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1318 subs->set_language (dcp::LanguageTag("en-US"));
1319 subs->add (simple_subtitle());
1320 subs->write (path / "subs2.mxf");
1321 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1322 cpl->reels()[1]->add(reel_subs);
1326 dcp::String::compose("libdcp %1", dcp::version),
1327 dcp::String::compose("libdcp %1", dcp::version),
1328 dcp::LocalTime().as_string(),
1332 check_verify_result (
1335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1342 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1344 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1345 auto constexpr reel_length = 192;
1346 auto dcp = make_simple (path, 2, reel_length);
1347 auto cpl = dcp->cpls()[0];
1350 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1351 ccaps->set_language (dcp::LanguageTag("de-DE"));
1352 ccaps->add (simple_subtitle());
1353 ccaps->write (path / "subs1.mxf");
1354 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1355 cpl->reels()[0]->add(reel_ccaps);
1359 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1360 ccaps->set_language (dcp::LanguageTag("en-US"));
1361 ccaps->add (simple_subtitle());
1362 ccaps->write (path / "subs2.mxf");
1363 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1364 cpl->reels()[1]->add(reel_ccaps);
1368 dcp::String::compose("libdcp %1", dcp::version),
1369 dcp::String::compose("libdcp %1", dcp::version),
1370 dcp::LocalTime().as_string(),
1374 check_verify_result (
1377 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1378 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1383 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1385 path dir = "build/test/verify_missing_subtitle_start_time";
1386 prepare_directory (dir);
1387 auto dcp = make_simple (dir, 1, 106);
1390 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1391 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1392 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1393 "<ContentTitleText>Content</ContentTitleText>"
1394 "<AnnotationText>Annotation</AnnotationText>"
1395 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1396 "<ReelNumber>1</ReelNumber>"
1397 "<Language>de-DE</Language>"
1398 "<EditRate>24 1</EditRate>"
1399 "<TimeCodeRate>24</TimeCodeRate>"
1400 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1402 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1403 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1404 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1410 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1411 BOOST_REQUIRE (xml_file);
1412 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1414 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1415 subs->write (dir / "subs.mxf");
1417 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1418 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1420 dcp::String::compose("libdcp %1", dcp::version),
1421 dcp::String::compose("libdcp %1", dcp::version),
1422 dcp::LocalTime().as_string(),
1426 check_verify_result (
1429 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1430 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1435 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1437 path dir = "build/test/verify_invalid_subtitle_start_time";
1438 prepare_directory (dir);
1439 auto dcp = make_simple (dir, 1, 106);
1442 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1443 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1444 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1445 "<ContentTitleText>Content</ContentTitleText>"
1446 "<AnnotationText>Annotation</AnnotationText>"
1447 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1448 "<ReelNumber>1</ReelNumber>"
1449 "<Language>de-DE</Language>"
1450 "<EditRate>24 1</EditRate>"
1451 "<TimeCodeRate>24</TimeCodeRate>"
1452 "<StartTime>00:00:02:00</StartTime>"
1453 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1455 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1456 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1457 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1463 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1464 BOOST_REQUIRE (xml_file);
1465 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1467 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1468 subs->write (dir / "subs.mxf");
1470 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1471 dcp->cpls().front()->reels().front()->add(reel_subs);
1473 dcp::String::compose("libdcp %1", dcp::version),
1474 dcp::String::compose("libdcp %1", dcp::version),
1475 dcp::LocalTime().as_string(),
1479 check_verify_result (
1482 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1483 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1491 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1494 , v_position(v_position_)
1506 shared_ptr<dcp::CPL>
1507 dcp_with_text (path dir, vector<TestText> subs)
1509 prepare_directory (dir);
1510 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1511 asset->set_start_time (dcp::Time());
1512 for (auto i: subs) {
1513 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1515 asset->set_language (dcp::LanguageTag("de-DE"));
1516 asset->write (dir / "subs.mxf");
1518 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1519 return write_dcp_with_single_asset (dir, reel_asset);
1523 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1525 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1526 /* Just too early */
1527 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1528 check_verify_result (
1531 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1532 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1538 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1540 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1541 /* Just late enough */
1542 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1543 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1547 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1549 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1550 prepare_directory (dir);
1552 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1553 asset1->set_start_time (dcp::Time());
1554 /* Just late enough */
1555 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1556 asset1->set_language (dcp::LanguageTag("de-DE"));
1557 asset1->write (dir / "subs1.mxf");
1558 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1559 auto reel1 = make_shared<dcp::Reel>();
1560 reel1->add (reel_asset1);
1561 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1562 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1563 reel1->add (markers1);
1565 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1566 asset2->set_start_time (dcp::Time());
1567 /* This would be too early on first reel but should be OK on the second */
1568 add_test_subtitle (asset2, 3, 4 * 24);
1569 asset2->set_language (dcp::LanguageTag("de-DE"));
1570 asset2->write (dir / "subs2.mxf");
1571 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1572 auto reel2 = make_shared<dcp::Reel>();
1573 reel2->add (reel_asset2);
1574 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1575 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1576 reel2->add (markers2);
1578 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1581 auto dcp = make_shared<dcp::DCP>(dir);
1584 dcp::String::compose("libdcp %1", dcp::version),
1585 dcp::String::compose("libdcp %1", dcp::version),
1586 dcp::LocalTime().as_string(),
1591 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1595 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1597 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1598 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1602 { 5 * 24 + 1, 6 * 24 },
1604 check_verify_result (
1607 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1608 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1613 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1615 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1616 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1620 { 5 * 24 + 16, 8 * 24 },
1622 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1626 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1628 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1629 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1630 check_verify_result (
1633 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1634 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1639 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1641 auto const dir = path("build/test/verify_valid_subtitle_duration");
1642 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1643 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1647 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1649 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1650 prepare_directory (dir);
1651 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1652 asset->set_start_time (dcp::Time());
1653 add_test_subtitle (asset, 0, 4 * 24);
1654 asset->set_language (dcp::LanguageTag("de-DE"));
1655 asset->write (dir / "subs.mxf");
1657 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1658 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1659 check_verify_result (
1662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1663 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1664 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1665 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1671 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1673 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1674 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1677 { 96, 200, 0.0, "We" },
1678 { 96, 200, 0.1, "have" },
1679 { 96, 200, 0.2, "four" },
1680 { 96, 200, 0.3, "lines" }
1682 check_verify_result (
1685 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1686 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1691 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1693 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1694 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1697 { 96, 200, 0.0, "We" },
1698 { 96, 200, 0.1, "have" },
1699 { 96, 200, 0.2, "four" },
1701 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1705 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1707 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1708 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1711 { 96, 300, 0.0, "We" },
1712 { 96, 300, 0.1, "have" },
1713 { 150, 180, 0.2, "four" },
1714 { 150, 180, 0.3, "lines" }
1716 check_verify_result (
1719 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1720 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1725 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1727 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1728 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1731 { 96, 300, 0.0, "We" },
1732 { 96, 300, 0.1, "have" },
1733 { 150, 180, 0.2, "four" },
1734 { 190, 250, 0.3, "lines" }
1736 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1740 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1742 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1743 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1746 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1748 check_verify_result (
1751 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1752 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1757 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1759 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1760 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1763 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1765 check_verify_result (
1768 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1769 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1774 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1776 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1777 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1780 { 96, 200, 0.0, "We" },
1781 { 96, 200, 0.1, "have" },
1782 { 96, 200, 0.2, "four" },
1783 { 96, 200, 0.3, "lines" }
1785 check_verify_result (
1788 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1789 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1794 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1796 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1797 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1800 { 96, 200, 0.0, "We" },
1801 { 96, 200, 0.1, "have" },
1802 { 96, 200, 0.2, "four" },
1804 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1808 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1810 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1811 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1814 { 96, 300, 0.0, "We" },
1815 { 96, 300, 0.1, "have" },
1816 { 150, 180, 0.2, "four" },
1817 { 150, 180, 0.3, "lines" }
1819 check_verify_result (
1822 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1823 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1828 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1830 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1831 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1834 { 96, 300, 0.0, "We" },
1835 { 96, 300, 0.1, "have" },
1836 { 150, 180, 0.2, "four" },
1837 { 190, 250, 0.3, "lines" }
1839 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1843 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1845 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1846 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1849 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1851 check_verify_result (
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1855 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1860 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1862 path const dir("build/test/verify_invalid_sound_frame_rate");
1863 prepare_directory (dir);
1865 auto picture = simple_picture (dir, "foo");
1866 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1867 auto reel = make_shared<dcp::Reel>();
1868 reel->add (reel_picture);
1869 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1870 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1871 reel->add (reel_sound);
1872 reel->add (simple_markers());
1873 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1875 auto dcp = make_shared<dcp::DCP>(dir);
1878 dcp::String::compose("libdcp %1", dcp::version),
1879 dcp::String::compose("libdcp %1", dcp::version),
1880 dcp::LocalTime().as_string(),
1884 check_verify_result (
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1893 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1895 path const dir("build/test/verify_missing_cpl_annotation_text");
1896 auto dcp = make_simple (dir);
1898 dcp::String::compose("libdcp %1", dcp::version),
1899 dcp::String::compose("libdcp %1", dcp::version),
1900 dcp::LocalTime().as_string(),
1904 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1906 auto const cpl = dcp->cpls()[0];
1909 BOOST_REQUIRE (cpl->file());
1910 Editor e(cpl->file().get());
1911 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1914 check_verify_result (
1917 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1918 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1923 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1925 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1926 auto dcp = make_simple (dir);
1928 dcp::String::compose("libdcp %1", dcp::version),
1929 dcp::String::compose("libdcp %1", dcp::version),
1930 dcp::LocalTime().as_string(),
1934 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1935 auto const cpl = dcp->cpls()[0];
1938 BOOST_REQUIRE (cpl->file());
1939 Editor e(cpl->file().get());
1940 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1943 check_verify_result (
1946 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1947 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1952 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1954 path const dir("build/test/verify_mismatched_asset_duration");
1955 prepare_directory (dir);
1956 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1957 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1959 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1960 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1962 auto reel = make_shared<dcp::Reel>(
1963 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1964 make_shared<dcp::ReelSoundAsset>(ms, 0)
1967 reel->add (simple_markers());
1972 dcp::String::compose("libdcp %1", dcp::version),
1973 dcp::String::compose("libdcp %1", dcp::version),
1974 dcp::LocalTime().as_string(),
1978 check_verify_result (
1981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1989 shared_ptr<dcp::CPL>
1990 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1992 prepare_directory (dir);
1993 auto dcp = make_shared<dcp::DCP>(dir);
1994 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1996 auto constexpr reel_length = 192;
1998 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1999 subs->set_language (dcp::LanguageTag("de-DE"));
2000 subs->set_start_time (dcp::Time());
2001 subs->add (simple_subtitle());
2002 subs->write (dir / "subs.mxf");
2003 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2005 auto reel1 = make_shared<dcp::Reel>(
2006 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2007 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2011 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2014 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2015 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2016 reel1->add (markers1);
2020 auto reel2 = make_shared<dcp::Reel>(
2021 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2022 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2026 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2029 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2030 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2031 reel2->add (markers2);
2037 dcp::String::compose("libdcp %1", dcp::version),
2038 dcp::String::compose("libdcp %1", dcp::version),
2039 dcp::LocalTime().as_string(),
2047 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2050 path dir ("build/test/missing_main_subtitle_from_some_reels");
2051 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2052 check_verify_result (
2055 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2056 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2062 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2063 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2064 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2068 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2069 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2070 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2076 shared_ptr<dcp::CPL>
2077 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2079 prepare_directory (dir);
2080 auto dcp = make_shared<dcp::DCP>(dir);
2081 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2083 auto constexpr reel_length = 192;
2085 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2086 subs->set_language (dcp::LanguageTag("de-DE"));
2087 subs->set_start_time (dcp::Time());
2088 subs->add (simple_subtitle());
2089 subs->write (dir / "subs.mxf");
2091 auto reel1 = make_shared<dcp::Reel>(
2092 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2093 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2096 for (int i = 0; i < caps_in_reel1; ++i) {
2097 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2100 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2101 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2102 reel1->add (markers1);
2106 auto reel2 = make_shared<dcp::Reel>(
2107 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2108 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2111 for (int i = 0; i < caps_in_reel2; ++i) {
2112 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2115 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2116 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2117 reel2->add (markers2);
2123 dcp::String::compose("libdcp %1", dcp::version),
2124 dcp::String::compose("libdcp %1", dcp::version),
2125 dcp::LocalTime().as_string(),
2133 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2136 path dir ("build/test/mismatched_closed_caption_asset_counts");
2137 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2138 check_verify_result (
2141 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2142 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2147 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2148 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2149 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2153 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2154 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2155 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2162 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2164 prepare_directory (dir);
2165 auto dcp = make_shared<dcp::DCP>(dir);
2166 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2168 auto constexpr reel_length = 192;
2170 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2171 subs->set_language (dcp::LanguageTag("de-DE"));
2172 subs->set_start_time (dcp::Time());
2173 subs->add (simple_subtitle());
2174 subs->write (dir / "subs.mxf");
2175 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2178 auto reel = make_shared<dcp::Reel>(
2179 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2180 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2183 reel->add (reel_text);
2185 reel->add (simple_markers(reel_length));
2191 dcp::String::compose("libdcp %1", dcp::version),
2192 dcp::String::compose("libdcp %1", dcp::version),
2193 dcp::LocalTime().as_string(),
2197 check_verify_result (
2200 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2201 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2206 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2208 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2209 "build/test/verify_subtitle_entry_point_must_be_present",
2210 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2211 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2212 asset->unset_entry_point ();
2216 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2217 "build/test/verify_subtitle_entry_point_must_be_zero",
2218 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2219 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2220 asset->set_entry_point (4);
2224 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2225 "build/test/verify_closed_caption_entry_point_must_be_present",
2226 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2227 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2228 asset->unset_entry_point ();
2232 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2233 "build/test/verify_closed_caption_entry_point_must_be_zero",
2234 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2235 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2236 asset->set_entry_point (9);
2242 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2246 path const dir("build/test/verify_missing_hash");
2247 auto dcp = make_simple (dir);
2249 dcp::String::compose("libdcp %1", dcp::version),
2250 dcp::String::compose("libdcp %1", dcp::version),
2251 dcp::LocalTime().as_string(),
2255 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2256 auto const cpl = dcp->cpls()[0];
2259 BOOST_REQUIRE (cpl->file());
2260 Editor e(cpl->file().get());
2261 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2264 check_verify_result (
2267 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2268 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2275 verify_markers_test (
2277 vector<pair<dcp::Marker, dcp::Time>> markers,
2278 vector<dcp::VerificationNote> test_notes
2281 auto dcp = make_simple (dir);
2282 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2283 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2284 for (auto const& i: markers) {
2285 markers_asset->set (i.first, i.second);
2287 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2289 dcp::String::compose("libdcp %1", dcp::version),
2290 dcp::String::compose("libdcp %1", dcp::version),
2291 dcp::LocalTime().as_string(),
2295 check_verify_result ({dir}, test_notes);
2299 BOOST_AUTO_TEST_CASE (verify_markers)
2301 verify_markers_test (
2302 "build/test/verify_markers_all_correct",
2304 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2305 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2306 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2307 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2312 verify_markers_test (
2313 "build/test/verify_markers_missing_ffec",
2315 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2316 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2317 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2320 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2323 verify_markers_test (
2324 "build/test/verify_markers_missing_ffmc",
2326 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2327 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2328 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2331 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2334 verify_markers_test (
2335 "build/test/verify_markers_missing_ffoc",
2337 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2338 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2339 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2342 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2345 verify_markers_test (
2346 "build/test/verify_markers_missing_lfoc",
2348 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2349 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2350 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2353 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2356 verify_markers_test (
2357 "build/test/verify_markers_incorrect_ffoc",
2359 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2360 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2361 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2362 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2365 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2368 verify_markers_test (
2369 "build/test/verify_markers_incorrect_lfoc",
2371 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2372 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2373 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2374 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2377 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2382 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2384 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2385 prepare_directory (dir);
2386 auto dcp = make_simple (dir);
2387 auto cpl = dcp->cpls()[0];
2388 cpl->unset_version_number();
2390 dcp::String::compose("libdcp %1", dcp::version),
2391 dcp::String::compose("libdcp %1", dcp::version),
2392 dcp::LocalTime().as_string(),
2396 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2400 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2402 path dir = "build/test/verify_missing_extension_metadata1";
2403 auto dcp = make_simple (dir);
2405 dcp::String::compose("libdcp %1", dcp::version),
2406 dcp::String::compose("libdcp %1", dcp::version),
2407 dcp::LocalTime().as_string(),
2411 auto cpl = dcp->cpls()[0];
2414 Editor e (cpl->file().get());
2415 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2418 check_verify_result (
2421 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2422 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2427 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2429 path dir = "build/test/verify_missing_extension_metadata2";
2430 auto dcp = make_simple (dir);
2432 dcp::String::compose("libdcp %1", dcp::version),
2433 dcp::String::compose("libdcp %1", dcp::version),
2434 dcp::LocalTime().as_string(),
2438 auto cpl = dcp->cpls()[0];
2441 Editor e (cpl->file().get());
2442 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2445 check_verify_result (
2448 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2449 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2454 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2456 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2457 auto dcp = make_simple (dir);
2459 dcp::String::compose("libdcp %1", dcp::version),
2460 dcp::String::compose("libdcp %1", dcp::version),
2461 dcp::LocalTime().as_string(),
2465 auto const cpl = dcp->cpls()[0];
2468 Editor e (cpl->file().get());
2469 e.replace ("<meta:Name>A", "<meta:NameX>A");
2470 e.replace ("n</meta:Name>", "n</meta:NameX>");
2473 check_verify_result (
2476 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2477 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2478 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2483 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2485 path dir = "build/test/verify_invalid_extension_metadata1";
2486 auto dcp = make_simple (dir);
2488 dcp::String::compose("libdcp %1", dcp::version),
2489 dcp::String::compose("libdcp %1", dcp::version),
2490 dcp::LocalTime().as_string(),
2494 auto cpl = dcp->cpls()[0];
2497 Editor e (cpl->file().get());
2498 e.replace ("Application", "Fred");
2501 check_verify_result (
2504 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2505 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2510 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2512 path dir = "build/test/verify_invalid_extension_metadata2";
2513 auto dcp = make_simple (dir);
2515 dcp::String::compose("libdcp %1", dcp::version),
2516 dcp::String::compose("libdcp %1", dcp::version),
2517 dcp::LocalTime().as_string(),
2521 auto cpl = dcp->cpls()[0];
2524 Editor e (cpl->file().get());
2525 e.replace ("DCP Constraints Profile", "Fred");
2528 check_verify_result (
2531 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2532 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2537 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2539 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2540 auto dcp = make_simple (dir);
2542 dcp::String::compose("libdcp %1", dcp::version),
2543 dcp::String::compose("libdcp %1", dcp::version),
2544 dcp::LocalTime().as_string(),
2548 auto const cpl = dcp->cpls()[0];
2551 Editor e (cpl->file().get());
2552 e.replace ("<meta:Value>", "<meta:ValueX>");
2553 e.replace ("</meta:Value>", "</meta:ValueX>");
2556 check_verify_result (
2559 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2560 { 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 },
2561 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2566 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2568 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2569 auto dcp = make_simple (dir);
2571 dcp::String::compose("libdcp %1", dcp::version),
2572 dcp::String::compose("libdcp %1", dcp::version),
2573 dcp::LocalTime().as_string(),
2577 auto const cpl = dcp->cpls()[0];
2580 Editor e (cpl->file().get());
2581 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2584 check_verify_result (
2587 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2588 { 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() },
2593 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2595 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2596 auto dcp = make_simple (dir);
2598 dcp::String::compose("libdcp %1", dcp::version),
2599 dcp::String::compose("libdcp %1", dcp::version),
2600 dcp::LocalTime().as_string(),
2604 auto const cpl = dcp->cpls()[0];
2607 Editor e (cpl->file().get());
2608 e.replace ("<meta:Property>", "<meta:PropertyX>");
2609 e.replace ("</meta:Property>", "</meta:PropertyX>");
2612 check_verify_result (
2615 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2616 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2617 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2622 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2624 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2625 auto dcp = make_simple (dir);
2627 dcp::String::compose("libdcp %1", dcp::version),
2628 dcp::String::compose("libdcp %1", dcp::version),
2629 dcp::LocalTime().as_string(),
2633 auto const cpl = dcp->cpls()[0];
2636 Editor e (cpl->file().get());
2637 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2638 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2641 check_verify_result (
2644 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2645 { 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 },
2646 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2652 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2654 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2655 prepare_directory (dir);
2656 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2657 copy_file (i.path(), dir / i.path().filename());
2660 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2661 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2665 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2668 check_verify_result (
2671 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2673 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2675 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2676 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2677 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2678 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2683 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2685 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2686 prepare_directory (dir);
2687 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2688 copy_file (i.path(), dir / i.path().filename());
2691 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2692 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2695 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2698 check_verify_result (
2701 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2702 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2704 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2705 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2707 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2712 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2714 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2715 prepare_directory (dir);
2716 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2717 copy_file (i.path(), dir / i.path().filename());
2721 Editor e (dir / dcp_test1_pkl);
2722 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2725 check_verify_result ({dir}, {});
2729 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2731 path dir ("build/test/verify_must_not_be_partially_encrypted");
2732 prepare_directory (dir);
2736 auto signer = make_shared<dcp::CertificateChain>();
2737 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2738 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2739 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2740 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2742 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2746 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2749 auto writer = mp->start_write (dir / "video.mxf", false);
2750 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2751 for (int i = 0; i < 24; ++i) {
2752 writer->write (j2c.data(), j2c.size());
2754 writer->finalize ();
2756 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2758 auto reel = make_shared<dcp::Reel>(
2759 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2760 make_shared<dcp::ReelSoundAsset>(ms, 0)
2763 reel->add (simple_markers());
2767 cpl->set_content_version (
2768 {"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"}
2770 cpl->set_annotation_text ("A Test DCP");
2771 cpl->set_issuer ("OpenDCP 0.0.25");
2772 cpl->set_creator ("OpenDCP 0.0.25");
2773 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2774 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2775 cpl->set_main_sound_sample_rate (48000);
2776 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2777 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2778 cpl->set_version_number (1);
2782 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2784 check_verify_result (
2787 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2792 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2794 vector<dcp::VerificationNote> notes;
2795 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"));
2796 auto reader = picture.start_read ();
2797 auto frame = reader->get_frame (0);
2798 verify_j2k (frame, notes);
2799 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2803 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2805 vector<dcp::VerificationNote> notes;
2806 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2807 auto reader = picture.start_read ();
2808 auto frame = reader->get_frame (0);
2809 verify_j2k (frame, notes);
2810 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2814 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2816 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2817 prepare_directory (dir);
2818 auto dcp = make_simple (dir);
2820 vector<dcp::VerificationNote> notes;
2821 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2822 auto reader = picture.start_read ();
2823 auto frame = reader->get_frame (0);
2824 verify_j2k (frame, notes);
2825 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2829 /** Check that ResourceID and the XML ID being different is spotted */
2830 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2832 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2833 prepare_directory (dir);
2835 ASDCP::WriterInfo writer_info;
2836 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2839 auto mxf_id = dcp::make_uuid ();
2840 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2841 BOOST_REQUIRE (c == Kumu::UUID_Length);
2843 auto resource_id = dcp::make_uuid ();
2844 ASDCP::TimedText::TimedTextDescriptor descriptor;
2845 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2846 DCP_ASSERT (c == Kumu::UUID_Length);
2848 auto xml_id = dcp::make_uuid ();
2849 ASDCP::TimedText::MXFWriter writer;
2850 auto subs_mxf = dir / "subs.mxf";
2851 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2852 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2853 writer.WriteTimedTextResource (dcp::String::compose(
2854 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2855 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2856 "<Id>urn:uuid:%1</Id>"
2857 "<ContentTitleText>Content</ContentTitleText>"
2858 "<AnnotationText>Annotation</AnnotationText>"
2859 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2860 "<ReelNumber>1</ReelNumber>"
2861 "<Language>en-US</Language>"
2862 "<EditRate>25 1</EditRate>"
2863 "<TimeCodeRate>25</TimeCodeRate>"
2864 "<StartTime>00:00:00:00</StartTime>"
2866 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2867 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2868 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2877 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2878 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2880 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2882 check_verify_result (
2885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2887 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2893 /** Check that ResourceID and the MXF ID being the same is spotted */
2894 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2896 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2897 prepare_directory (dir);
2899 ASDCP::WriterInfo writer_info;
2900 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2903 auto mxf_id = dcp::make_uuid ();
2904 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2905 BOOST_REQUIRE (c == Kumu::UUID_Length);
2907 auto resource_id = mxf_id;
2908 ASDCP::TimedText::TimedTextDescriptor descriptor;
2909 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2910 DCP_ASSERT (c == Kumu::UUID_Length);
2912 auto xml_id = resource_id;
2913 ASDCP::TimedText::MXFWriter writer;
2914 auto subs_mxf = dir / "subs.mxf";
2915 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2916 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2917 writer.WriteTimedTextResource (dcp::String::compose(
2918 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2919 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2920 "<Id>urn:uuid:%1</Id>"
2921 "<ContentTitleText>Content</ContentTitleText>"
2922 "<AnnotationText>Annotation</AnnotationText>"
2923 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2924 "<ReelNumber>1</ReelNumber>"
2925 "<Language>en-US</Language>"
2926 "<EditRate>25 1</EditRate>"
2927 "<TimeCodeRate>25</TimeCodeRate>"
2928 "<StartTime>00:00:00:00</StartTime>"
2930 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2931 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2932 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2941 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2942 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2944 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2946 check_verify_result (
2949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2950 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2951 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2952 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2957 /** Check a DCP with a 3D asset marked as 2D */
2958 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
2960 check_verify_result (
2961 { private_test / "data" / "xm" },
2964 dcp::VerificationNote::Type::WARNING,
2965 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
2968 dcp::VerificationNote::Type::BV21_ERROR,
2969 dcp::VerificationNote::Code::INVALID_STANDARD