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;
199 dump_notes (vector<dcp::VerificationNote> const & notes)
201 for (auto i: notes) {
202 std::cout << dcp::note_to_string(i) << "\n";
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 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
710 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
711 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
712 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
714 check_verify_result (
717 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
719 dcp::VerificationNote::Type::ERROR,
720 dcp::VerificationNote::Code::INVALID_XML,
721 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
726 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
731 BOOST_AUTO_TEST_CASE (verify_external_asset)
733 path const ov_dir("build/test/verify_external_asset");
734 prepare_directory (ov_dir);
736 auto image = black_image ();
737 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
738 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
739 dcp_from_frame (frame, ov_dir);
741 dcp::DCP ov (ov_dir);
744 path const vf_dir("build/test/verify_external_asset_vf");
745 prepare_directory (vf_dir);
747 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
748 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
750 check_verify_result (
753 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
754 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
759 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
761 path const dir("build/test/verify_valid_cpl_metadata");
762 prepare_directory (dir);
764 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
765 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
766 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
768 auto reel = make_shared<dcp::Reel>();
769 reel->add (reel_asset);
771 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
772 reel->add (simple_markers(16 * 24));
774 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
776 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
777 cpl->set_main_sound_sample_rate (48000);
778 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
779 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
780 cpl->set_version_number (1);
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::String::compose("libdcp %1", dcp::version),
787 dcp::LocalTime().as_string(),
793 path find_cpl (path dir)
795 for (auto i: directory_iterator(dir)) {
796 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
801 BOOST_REQUIRE (false);
806 /* DCP with invalid CompositionMetadataAsset */
807 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
809 using namespace boost::filesystem;
811 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
812 prepare_directory (dir);
814 auto reel = make_shared<dcp::Reel>();
815 reel->add (black_picture_asset(dir));
816 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
818 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
819 cpl->set_main_sound_sample_rate (48000);
820 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
821 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
822 cpl->set_version_number (1);
824 reel->add (simple_markers());
829 dcp::String::compose("libdcp %1", dcp::version),
830 dcp::String::compose("libdcp %1", dcp::version),
831 dcp::LocalTime().as_string(),
836 Editor e (find_cpl(dir));
837 e.replace ("MainSound", "MainSoundX");
840 check_verify_result (
843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
846 dcp::VerificationNote::Type::ERROR,
847 dcp::VerificationNote::Code::INVALID_XML,
848 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
849 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
850 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
851 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
852 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
853 "ExtensionMetadataList?,)'"),
854 canonical(cpl->file().get()),
857 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
862 /* DCP with invalid CompositionMetadataAsset */
863 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
865 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
866 prepare_directory (dir);
868 auto reel = make_shared<dcp::Reel>();
869 reel->add (black_picture_asset(dir));
870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
872 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873 cpl->set_main_sound_sample_rate (48000);
874 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
880 dcp::String::compose("libdcp %1", dcp::version),
881 dcp::String::compose("libdcp %1", dcp::version),
882 dcp::LocalTime().as_string(),
887 Editor e (find_cpl(dir));
888 e.replace ("meta:Width", "meta:WidthX");
891 check_verify_result (
893 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
898 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
900 path const dir("build/test/verify_invalid_language1");
901 prepare_directory (dir);
902 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
903 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
904 asset->_language = "wrong-andbad";
905 asset->write (dir / "subs.mxf");
906 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
907 reel_asset->_language = "badlang";
908 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
910 check_verify_result (
913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
920 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
921 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
923 path const dir("build/test/verify_invalid_language2");
924 prepare_directory (dir);
925 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
926 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
927 asset->_language = "wrong-andbad";
928 asset->write (dir / "subs.mxf");
929 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
930 reel_asset->_language = "badlang";
931 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
933 check_verify_result (
936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
943 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
944 * the release territory.
946 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
948 path const dir("build/test/verify_invalid_language3");
949 prepare_directory (dir);
951 auto picture = simple_picture (dir, "foo");
952 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
953 auto reel = make_shared<dcp::Reel>();
954 reel->add (reel_picture);
955 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
956 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
957 reel->add (reel_sound);
958 reel->add (simple_markers());
960 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
962 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
963 cpl->_additional_subtitle_languages.push_back("andso-is-this");
964 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
965 cpl->set_main_sound_sample_rate (48000);
966 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
967 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
968 cpl->set_version_number (1);
969 cpl->_release_territory = "fred-jim";
970 auto dcp = make_shared<dcp::DCP>(dir);
973 dcp::String::compose("libdcp %1", dcp::version),
974 dcp::String::compose("libdcp %1", dcp::version),
975 dcp::LocalTime().as_string(),
979 check_verify_result (
982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
991 vector<dcp::VerificationNote>
992 check_picture_size (int width, int height, int frame_rate, bool three_d)
994 using namespace boost::filesystem;
996 path dcp_path = "build/test/verify_picture_test";
997 prepare_directory (dcp_path);
999 shared_ptr<dcp::PictureAsset> mp;
1001 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1003 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1005 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1007 auto image = black_image (dcp::Size(width, height));
1008 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1009 int const length = three_d ? frame_rate * 2 : frame_rate;
1010 for (int i = 0; i < length; ++i) {
1011 picture_writer->write (j2c.data(), j2c.size());
1013 picture_writer->finalize ();
1015 auto d = make_shared<dcp::DCP>(dcp_path);
1016 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1017 cpl->set_annotation_text ("A Test DCP");
1018 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1019 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1020 cpl->set_main_sound_sample_rate (48000);
1021 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1022 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1023 cpl->set_version_number (1);
1025 auto reel = make_shared<dcp::Reel>();
1028 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1030 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1033 reel->add (simple_markers(frame_rate));
1039 dcp::String::compose("libdcp %1", dcp::version),
1040 dcp::String::compose("libdcp %1", dcp::version),
1041 dcp::LocalTime().as_string(),
1045 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1051 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1053 auto notes = check_picture_size(width, height, frame_rate, three_d);
1054 BOOST_CHECK_EQUAL (notes.size(), 0U);
1060 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1062 auto notes = check_picture_size(width, height, frame_rate, three_d);
1063 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1064 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1065 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1071 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1073 auto notes = check_picture_size(width, height, frame_rate, three_d);
1074 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1075 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1076 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1082 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1084 auto notes = check_picture_size(width, height, frame_rate, three_d);
1085 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1086 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1087 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1091 BOOST_AUTO_TEST_CASE (verify_picture_size)
1093 using namespace boost::filesystem;
1096 check_picture_size_ok (2048, 858, 24, false);
1097 check_picture_size_ok (2048, 858, 25, false);
1098 check_picture_size_ok (2048, 858, 48, false);
1099 check_picture_size_ok (2048, 858, 24, true);
1100 check_picture_size_ok (2048, 858, 25, true);
1101 check_picture_size_ok (2048, 858, 48, true);
1104 check_picture_size_ok (1998, 1080, 24, false);
1105 check_picture_size_ok (1998, 1080, 25, false);
1106 check_picture_size_ok (1998, 1080, 48, false);
1107 check_picture_size_ok (1998, 1080, 24, true);
1108 check_picture_size_ok (1998, 1080, 25, true);
1109 check_picture_size_ok (1998, 1080, 48, true);
1112 check_picture_size_ok (4096, 1716, 24, false);
1115 check_picture_size_ok (3996, 2160, 24, false);
1117 /* Bad frame size */
1118 check_picture_size_bad_frame_size (2050, 858, 24, false);
1119 check_picture_size_bad_frame_size (2048, 658, 25, false);
1120 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1121 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1123 /* Bad 2K frame rate */
1124 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1125 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1126 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1128 /* Bad 4K frame rate */
1129 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1130 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1133 auto notes = check_picture_size(3996, 2160, 24, true);
1134 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1135 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1136 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1142 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1145 make_shared<dcp::SubtitleString>(
1153 dcp::Time(start_frame, 24, 24),
1154 dcp::Time(end_frame, 24, 24),
1156 dcp::HAlign::CENTER,
1158 dcp::VAlign::CENTER,
1159 dcp::Direction::LTR,
1170 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1172 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1173 prepare_directory (dir);
1175 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1176 for (int i = 0; i < 2048; ++i) {
1177 add_test_subtitle (asset, i * 24, i * 24 + 20);
1179 asset->set_language (dcp::LanguageTag("de-DE"));
1180 asset->write (dir / "subs.mxf");
1181 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1182 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1184 check_verify_result (
1187 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1189 dcp::VerificationNote::Type::BV21_ERROR,
1190 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1192 canonical(dir / "subs.mxf")
1194 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1195 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1201 shared_ptr<dcp::SMPTESubtitleAsset>
1202 make_large_subtitle_asset (path font_file)
1204 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1205 dcp::ArrayData big_fake_font(1024 * 1024);
1206 big_fake_font.write (font_file);
1207 for (int i = 0; i < 116; ++i) {
1208 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1216 verify_timed_text_asset_too_large (string name)
1218 auto const dir = path("build/test") / name;
1219 prepare_directory (dir);
1220 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1221 add_test_subtitle (asset, 0, 240);
1222 asset->set_language (dcp::LanguageTag("de-DE"));
1223 asset->write (dir / "subs.mxf");
1225 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1226 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1228 check_verify_result (
1231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1233 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1234 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1240 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1242 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1243 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1247 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1249 path dir = "build/test/verify_missing_subtitle_language";
1250 prepare_directory (dir);
1251 auto dcp = make_simple (dir, 1, 106);
1254 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1255 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1256 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1257 "<ContentTitleText>Content</ContentTitleText>"
1258 "<AnnotationText>Annotation</AnnotationText>"
1259 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1260 "<ReelNumber>1</ReelNumber>"
1261 "<EditRate>24 1</EditRate>"
1262 "<TimeCodeRate>24</TimeCodeRate>"
1263 "<StartTime>00:00:00:00</StartTime>"
1264 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1266 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1267 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1268 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1274 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1275 BOOST_REQUIRE (xml_file);
1276 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1278 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1279 subs->write (dir / "subs.mxf");
1281 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1282 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1284 dcp::String::compose("libdcp %1", dcp::version),
1285 dcp::String::compose("libdcp %1", dcp::version),
1286 dcp::LocalTime().as_string(),
1290 check_verify_result (
1293 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1294 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1299 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1301 path path ("build/test/verify_mismatched_subtitle_languages");
1302 auto constexpr reel_length = 192;
1303 auto dcp = make_simple (path, 2, reel_length);
1304 auto cpl = dcp->cpls()[0];
1307 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1308 subs->set_language (dcp::LanguageTag("de-DE"));
1309 subs->add (simple_subtitle());
1310 subs->write (path / "subs1.mxf");
1311 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1312 cpl->reels()[0]->add(reel_subs);
1316 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1317 subs->set_language (dcp::LanguageTag("en-US"));
1318 subs->add (simple_subtitle());
1319 subs->write (path / "subs2.mxf");
1320 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1321 cpl->reels()[1]->add(reel_subs);
1325 dcp::String::compose("libdcp %1", dcp::version),
1326 dcp::String::compose("libdcp %1", dcp::version),
1327 dcp::LocalTime().as_string(),
1331 check_verify_result (
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1336 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1341 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1343 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1344 auto constexpr reel_length = 192;
1345 auto dcp = make_simple (path, 2, reel_length);
1346 auto cpl = dcp->cpls()[0];
1349 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1350 ccaps->set_language (dcp::LanguageTag("de-DE"));
1351 ccaps->add (simple_subtitle());
1352 ccaps->write (path / "subs1.mxf");
1353 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1354 cpl->reels()[0]->add(reel_ccaps);
1358 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1359 ccaps->set_language (dcp::LanguageTag("en-US"));
1360 ccaps->add (simple_subtitle());
1361 ccaps->write (path / "subs2.mxf");
1362 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1363 cpl->reels()[1]->add(reel_ccaps);
1367 dcp::String::compose("libdcp %1", dcp::version),
1368 dcp::String::compose("libdcp %1", dcp::version),
1369 dcp::LocalTime().as_string(),
1373 check_verify_result (
1376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1377 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1382 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1384 path dir = "build/test/verify_missing_subtitle_start_time";
1385 prepare_directory (dir);
1386 auto dcp = make_simple (dir, 1, 106);
1389 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1390 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1391 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1392 "<ContentTitleText>Content</ContentTitleText>"
1393 "<AnnotationText>Annotation</AnnotationText>"
1394 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1395 "<ReelNumber>1</ReelNumber>"
1396 "<Language>de-DE</Language>"
1397 "<EditRate>24 1</EditRate>"
1398 "<TimeCodeRate>24</TimeCodeRate>"
1399 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1401 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1402 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1403 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1409 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1410 BOOST_REQUIRE (xml_file);
1411 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1413 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1414 subs->write (dir / "subs.mxf");
1416 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1417 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1419 dcp::String::compose("libdcp %1", dcp::version),
1420 dcp::String::compose("libdcp %1", dcp::version),
1421 dcp::LocalTime().as_string(),
1425 check_verify_result (
1428 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1429 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1434 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1436 path dir = "build/test/verify_invalid_subtitle_start_time";
1437 prepare_directory (dir);
1438 auto dcp = make_simple (dir, 1, 106);
1441 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1442 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1443 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1444 "<ContentTitleText>Content</ContentTitleText>"
1445 "<AnnotationText>Annotation</AnnotationText>"
1446 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1447 "<ReelNumber>1</ReelNumber>"
1448 "<Language>de-DE</Language>"
1449 "<EditRate>24 1</EditRate>"
1450 "<TimeCodeRate>24</TimeCodeRate>"
1451 "<StartTime>00:00:02:00</StartTime>"
1452 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1454 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1455 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1456 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1462 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1463 BOOST_REQUIRE (xml_file);
1464 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1466 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1467 subs->write (dir / "subs.mxf");
1469 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1470 dcp->cpls().front()->reels().front()->add(reel_subs);
1472 dcp::String::compose("libdcp %1", dcp::version),
1473 dcp::String::compose("libdcp %1", dcp::version),
1474 dcp::LocalTime().as_string(),
1478 check_verify_result (
1481 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1482 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1490 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1493 , v_position(v_position_)
1505 shared_ptr<dcp::CPL>
1506 dcp_with_text (path dir, vector<TestText> subs)
1508 prepare_directory (dir);
1509 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1510 asset->set_start_time (dcp::Time());
1511 for (auto i: subs) {
1512 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1514 asset->set_language (dcp::LanguageTag("de-DE"));
1515 asset->write (dir / "subs.mxf");
1517 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1518 return write_dcp_with_single_asset (dir, reel_asset);
1522 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1524 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1525 /* Just too early */
1526 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1527 check_verify_result (
1530 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1531 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1537 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1539 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1540 /* Just late enough */
1541 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1542 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1546 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1548 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1549 prepare_directory (dir);
1551 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1552 asset1->set_start_time (dcp::Time());
1553 /* Just late enough */
1554 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1555 asset1->set_language (dcp::LanguageTag("de-DE"));
1556 asset1->write (dir / "subs1.mxf");
1557 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1558 auto reel1 = make_shared<dcp::Reel>();
1559 reel1->add (reel_asset1);
1560 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1561 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1562 reel1->add (markers1);
1564 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1565 asset2->set_start_time (dcp::Time());
1566 /* This would be too early on first reel but should be OK on the second */
1567 add_test_subtitle (asset2, 3, 4 * 24);
1568 asset2->set_language (dcp::LanguageTag("de-DE"));
1569 asset2->write (dir / "subs2.mxf");
1570 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1571 auto reel2 = make_shared<dcp::Reel>();
1572 reel2->add (reel_asset2);
1573 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1574 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1575 reel2->add (markers2);
1577 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1580 auto dcp = make_shared<dcp::DCP>(dir);
1583 dcp::String::compose("libdcp %1", dcp::version),
1584 dcp::String::compose("libdcp %1", dcp::version),
1585 dcp::LocalTime().as_string(),
1590 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1594 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1596 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1597 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1601 { 5 * 24 + 1, 6 * 24 },
1603 check_verify_result (
1606 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1607 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1612 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1614 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1615 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1619 { 5 * 24 + 16, 8 * 24 },
1621 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1625 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1627 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1628 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1629 check_verify_result (
1632 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1633 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1638 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1640 auto const dir = path("build/test/verify_valid_subtitle_duration");
1641 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1642 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1646 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1648 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1649 prepare_directory (dir);
1650 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1651 asset->set_start_time (dcp::Time());
1652 add_test_subtitle (asset, 0, 4 * 24);
1653 asset->set_language (dcp::LanguageTag("de-DE"));
1654 asset->write (dir / "subs.mxf");
1656 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1657 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1658 check_verify_result (
1661 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1662 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1663 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1664 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1670 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1672 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1673 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1676 { 96, 200, 0.0, "We" },
1677 { 96, 200, 0.1, "have" },
1678 { 96, 200, 0.2, "four" },
1679 { 96, 200, 0.3, "lines" }
1681 check_verify_result (
1684 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1685 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1690 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1692 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1693 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1696 { 96, 200, 0.0, "We" },
1697 { 96, 200, 0.1, "have" },
1698 { 96, 200, 0.2, "four" },
1700 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1704 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1706 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1707 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1710 { 96, 300, 0.0, "We" },
1711 { 96, 300, 0.1, "have" },
1712 { 150, 180, 0.2, "four" },
1713 { 150, 180, 0.3, "lines" }
1715 check_verify_result (
1718 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1719 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1724 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1726 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1727 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1730 { 96, 300, 0.0, "We" },
1731 { 96, 300, 0.1, "have" },
1732 { 150, 180, 0.2, "four" },
1733 { 190, 250, 0.3, "lines" }
1735 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1739 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1741 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1742 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1745 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1756 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1758 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1759 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1762 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1764 check_verify_result (
1767 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1768 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1773 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1775 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1776 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1779 { 96, 200, 0.0, "We" },
1780 { 96, 200, 0.1, "have" },
1781 { 96, 200, 0.2, "four" },
1782 { 96, 200, 0.3, "lines" }
1784 check_verify_result (
1787 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1788 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1793 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1795 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1796 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1799 { 96, 200, 0.0, "We" },
1800 { 96, 200, 0.1, "have" },
1801 { 96, 200, 0.2, "four" },
1803 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1807 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1809 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1810 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1813 { 96, 300, 0.0, "We" },
1814 { 96, 300, 0.1, "have" },
1815 { 150, 180, 0.2, "four" },
1816 { 150, 180, 0.3, "lines" }
1818 check_verify_result (
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1822 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1827 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1829 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1830 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1833 { 96, 300, 0.0, "We" },
1834 { 96, 300, 0.1, "have" },
1835 { 150, 180, 0.2, "four" },
1836 { 190, 250, 0.3, "lines" }
1838 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1842 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1844 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1845 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1848 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1850 check_verify_result (
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1854 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1859 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1861 path const dir("build/test/verify_invalid_sound_frame_rate");
1862 prepare_directory (dir);
1864 auto picture = simple_picture (dir, "foo");
1865 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1866 auto reel = make_shared<dcp::Reel>();
1867 reel->add (reel_picture);
1868 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1869 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1870 reel->add (reel_sound);
1871 reel->add (simple_markers());
1872 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1874 auto dcp = make_shared<dcp::DCP>(dir);
1877 dcp::String::compose("libdcp %1", dcp::version),
1878 dcp::String::compose("libdcp %1", dcp::version),
1879 dcp::LocalTime().as_string(),
1883 check_verify_result (
1886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1892 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1894 path const dir("build/test/verify_missing_cpl_annotation_text");
1895 auto dcp = make_simple (dir);
1897 dcp::String::compose("libdcp %1", dcp::version),
1898 dcp::String::compose("libdcp %1", dcp::version),
1899 dcp::LocalTime().as_string(),
1903 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1905 auto const cpl = dcp->cpls()[0];
1908 BOOST_REQUIRE (cpl->file());
1909 Editor e(cpl->file().get());
1910 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1913 check_verify_result (
1916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1917 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1922 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1924 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1925 auto dcp = make_simple (dir);
1927 dcp::String::compose("libdcp %1", dcp::version),
1928 dcp::String::compose("libdcp %1", dcp::version),
1929 dcp::LocalTime().as_string(),
1933 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1934 auto const cpl = dcp->cpls()[0];
1937 BOOST_REQUIRE (cpl->file());
1938 Editor e(cpl->file().get());
1939 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1942 check_verify_result (
1945 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1946 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1951 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1953 path const dir("build/test/verify_mismatched_asset_duration");
1954 prepare_directory (dir);
1955 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1956 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1958 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1959 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1961 auto reel = make_shared<dcp::Reel>(
1962 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1963 make_shared<dcp::ReelSoundAsset>(ms, 0)
1966 reel->add (simple_markers());
1971 dcp::String::compose("libdcp %1", dcp::version),
1972 dcp::String::compose("libdcp %1", dcp::version),
1973 dcp::LocalTime().as_string(),
1977 check_verify_result (
1980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1988 shared_ptr<dcp::CPL>
1989 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1991 prepare_directory (dir);
1992 auto dcp = make_shared<dcp::DCP>(dir);
1993 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1995 auto constexpr reel_length = 192;
1997 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1998 subs->set_language (dcp::LanguageTag("de-DE"));
1999 subs->set_start_time (dcp::Time());
2000 subs->add (simple_subtitle());
2001 subs->write (dir / "subs.mxf");
2002 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2004 auto reel1 = make_shared<dcp::Reel>(
2005 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2006 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2010 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2013 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2014 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2015 reel1->add (markers1);
2019 auto reel2 = make_shared<dcp::Reel>(
2020 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2021 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2025 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2028 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2029 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2030 reel2->add (markers2);
2036 dcp::String::compose("libdcp %1", dcp::version),
2037 dcp::String::compose("libdcp %1", dcp::version),
2038 dcp::LocalTime().as_string(),
2046 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2049 path dir ("build/test/missing_main_subtitle_from_some_reels");
2050 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2051 check_verify_result (
2054 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2055 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2061 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2062 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2063 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2067 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2068 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2069 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2075 shared_ptr<dcp::CPL>
2076 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2078 prepare_directory (dir);
2079 auto dcp = make_shared<dcp::DCP>(dir);
2080 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2082 auto constexpr reel_length = 192;
2084 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2085 subs->set_language (dcp::LanguageTag("de-DE"));
2086 subs->set_start_time (dcp::Time());
2087 subs->add (simple_subtitle());
2088 subs->write (dir / "subs.mxf");
2090 auto reel1 = make_shared<dcp::Reel>(
2091 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2092 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2095 for (int i = 0; i < caps_in_reel1; ++i) {
2096 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2099 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2100 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2101 reel1->add (markers1);
2105 auto reel2 = make_shared<dcp::Reel>(
2106 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2107 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2110 for (int i = 0; i < caps_in_reel2; ++i) {
2111 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2114 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2115 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2116 reel2->add (markers2);
2122 dcp::String::compose("libdcp %1", dcp::version),
2123 dcp::String::compose("libdcp %1", dcp::version),
2124 dcp::LocalTime().as_string(),
2132 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2135 path dir ("build/test/mismatched_closed_caption_asset_counts");
2136 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2137 check_verify_result (
2140 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2141 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2146 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2147 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2148 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2152 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2153 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2154 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2161 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2163 prepare_directory (dir);
2164 auto dcp = make_shared<dcp::DCP>(dir);
2165 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2167 auto constexpr reel_length = 192;
2169 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2170 subs->set_language (dcp::LanguageTag("de-DE"));
2171 subs->set_start_time (dcp::Time());
2172 subs->add (simple_subtitle());
2173 subs->write (dir / "subs.mxf");
2174 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2177 auto reel = make_shared<dcp::Reel>(
2178 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2179 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2182 reel->add (reel_text);
2184 reel->add (simple_markers(reel_length));
2190 dcp::String::compose("libdcp %1", dcp::version),
2191 dcp::String::compose("libdcp %1", dcp::version),
2192 dcp::LocalTime().as_string(),
2196 check_verify_result (
2199 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2200 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2205 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2207 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2208 "build/test/verify_subtitle_entry_point_must_be_present",
2209 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2210 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2211 asset->unset_entry_point ();
2215 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2216 "build/test/verify_subtitle_entry_point_must_be_zero",
2217 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2218 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2219 asset->set_entry_point (4);
2223 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2224 "build/test/verify_closed_caption_entry_point_must_be_present",
2225 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2226 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2227 asset->unset_entry_point ();
2231 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2232 "build/test/verify_closed_caption_entry_point_must_be_zero",
2233 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2234 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2235 asset->set_entry_point (9);
2241 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2245 path const dir("build/test/verify_missing_hash");
2246 auto dcp = make_simple (dir);
2248 dcp::String::compose("libdcp %1", dcp::version),
2249 dcp::String::compose("libdcp %1", dcp::version),
2250 dcp::LocalTime().as_string(),
2254 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2255 auto const cpl = dcp->cpls()[0];
2258 BOOST_REQUIRE (cpl->file());
2259 Editor e(cpl->file().get());
2260 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2263 check_verify_result (
2266 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2267 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2274 verify_markers_test (
2276 vector<pair<dcp::Marker, dcp::Time>> markers,
2277 vector<dcp::VerificationNote> test_notes
2280 auto dcp = make_simple (dir);
2281 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2282 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2283 for (auto const& i: markers) {
2284 markers_asset->set (i.first, i.second);
2286 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2288 dcp::String::compose("libdcp %1", dcp::version),
2289 dcp::String::compose("libdcp %1", dcp::version),
2290 dcp::LocalTime().as_string(),
2294 check_verify_result ({dir}, test_notes);
2298 BOOST_AUTO_TEST_CASE (verify_markers)
2300 verify_markers_test (
2301 "build/test/verify_markers_all_correct",
2303 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2304 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2305 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2306 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2311 verify_markers_test (
2312 "build/test/verify_markers_missing_ffec",
2314 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2315 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2316 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2319 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2322 verify_markers_test (
2323 "build/test/verify_markers_missing_ffmc",
2325 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2326 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2327 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2330 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2333 verify_markers_test (
2334 "build/test/verify_markers_missing_ffoc",
2336 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2337 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2338 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2341 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2344 verify_markers_test (
2345 "build/test/verify_markers_missing_lfoc",
2347 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2348 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2349 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2352 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2355 verify_markers_test (
2356 "build/test/verify_markers_incorrect_ffoc",
2358 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2359 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2360 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2361 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2364 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2367 verify_markers_test (
2368 "build/test/verify_markers_incorrect_lfoc",
2370 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2371 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2372 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2373 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2376 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2381 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2383 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2384 prepare_directory (dir);
2385 auto dcp = make_simple (dir);
2386 auto cpl = dcp->cpls()[0];
2387 cpl->unset_version_number();
2389 dcp::String::compose("libdcp %1", dcp::version),
2390 dcp::String::compose("libdcp %1", dcp::version),
2391 dcp::LocalTime().as_string(),
2395 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2399 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2401 path dir = "build/test/verify_missing_extension_metadata1";
2402 auto dcp = make_simple (dir);
2404 dcp::String::compose("libdcp %1", dcp::version),
2405 dcp::String::compose("libdcp %1", dcp::version),
2406 dcp::LocalTime().as_string(),
2410 auto cpl = dcp->cpls()[0];
2413 Editor e (cpl->file().get());
2414 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2417 check_verify_result (
2420 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2421 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2426 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2428 path dir = "build/test/verify_missing_extension_metadata2";
2429 auto dcp = make_simple (dir);
2431 dcp::String::compose("libdcp %1", dcp::version),
2432 dcp::String::compose("libdcp %1", dcp::version),
2433 dcp::LocalTime().as_string(),
2437 auto cpl = dcp->cpls()[0];
2440 Editor e (cpl->file().get());
2441 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2444 check_verify_result (
2447 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2448 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2453 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2455 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2456 auto dcp = make_simple (dir);
2458 dcp::String::compose("libdcp %1", dcp::version),
2459 dcp::String::compose("libdcp %1", dcp::version),
2460 dcp::LocalTime().as_string(),
2464 auto const cpl = dcp->cpls()[0];
2467 Editor e (cpl->file().get());
2468 e.replace ("<meta:Name>A", "<meta:NameX>A");
2469 e.replace ("n</meta:Name>", "n</meta:NameX>");
2472 check_verify_result (
2475 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2476 { 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 },
2477 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2482 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2484 path dir = "build/test/verify_invalid_extension_metadata1";
2485 auto dcp = make_simple (dir);
2487 dcp::String::compose("libdcp %1", dcp::version),
2488 dcp::String::compose("libdcp %1", dcp::version),
2489 dcp::LocalTime().as_string(),
2493 auto cpl = dcp->cpls()[0];
2496 Editor e (cpl->file().get());
2497 e.replace ("Application", "Fred");
2500 check_verify_result (
2503 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2504 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2509 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2511 path dir = "build/test/verify_invalid_extension_metadata2";
2512 auto dcp = make_simple (dir);
2514 dcp::String::compose("libdcp %1", dcp::version),
2515 dcp::String::compose("libdcp %1", dcp::version),
2516 dcp::LocalTime().as_string(),
2520 auto cpl = dcp->cpls()[0];
2523 Editor e (cpl->file().get());
2524 e.replace ("DCP Constraints Profile", "Fred");
2527 check_verify_result (
2530 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2531 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2536 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2538 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2539 auto dcp = make_simple (dir);
2541 dcp::String::compose("libdcp %1", dcp::version),
2542 dcp::String::compose("libdcp %1", dcp::version),
2543 dcp::LocalTime().as_string(),
2547 auto const cpl = dcp->cpls()[0];
2550 Editor e (cpl->file().get());
2551 e.replace ("<meta:Value>", "<meta:ValueX>");
2552 e.replace ("</meta:Value>", "</meta:ValueX>");
2555 check_verify_result (
2558 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2559 { 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 },
2560 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2565 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2567 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2568 auto dcp = make_simple (dir);
2570 dcp::String::compose("libdcp %1", dcp::version),
2571 dcp::String::compose("libdcp %1", dcp::version),
2572 dcp::LocalTime().as_string(),
2576 auto const cpl = dcp->cpls()[0];
2579 Editor e (cpl->file().get());
2580 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2583 check_verify_result (
2586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2587 { 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() },
2592 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2594 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2595 auto dcp = make_simple (dir);
2597 dcp::String::compose("libdcp %1", dcp::version),
2598 dcp::String::compose("libdcp %1", dcp::version),
2599 dcp::LocalTime().as_string(),
2603 auto const cpl = dcp->cpls()[0];
2606 Editor e (cpl->file().get());
2607 e.replace ("<meta:Property>", "<meta:PropertyX>");
2608 e.replace ("</meta:Property>", "</meta:PropertyX>");
2611 check_verify_result (
2614 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2615 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2616 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2621 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2623 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2624 auto dcp = make_simple (dir);
2626 dcp::String::compose("libdcp %1", dcp::version),
2627 dcp::String::compose("libdcp %1", dcp::version),
2628 dcp::LocalTime().as_string(),
2632 auto const cpl = dcp->cpls()[0];
2635 Editor e (cpl->file().get());
2636 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2637 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2640 check_verify_result (
2643 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2644 { 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 },
2645 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2651 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2653 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2654 prepare_directory (dir);
2655 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2656 copy_file (i.path(), dir / i.path().filename());
2659 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2660 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2664 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2667 check_verify_result (
2670 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2673 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2674 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2675 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2677 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2682 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2684 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2685 prepare_directory (dir);
2686 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2687 copy_file (i.path(), dir / i.path().filename());
2690 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2691 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2694 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2697 check_verify_result (
2700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2701 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2702 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2703 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2704 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2711 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2713 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2714 prepare_directory (dir);
2715 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2716 copy_file (i.path(), dir / i.path().filename());
2720 Editor e (dir / dcp_test1_pkl);
2721 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2724 check_verify_result ({dir}, {});
2728 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2730 path dir ("build/test/verify_must_not_be_partially_encrypted");
2731 prepare_directory (dir);
2735 auto signer = make_shared<dcp::CertificateChain>();
2736 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2737 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2738 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2739 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2741 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2745 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2748 auto writer = mp->start_write (dir / "video.mxf", false);
2749 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2750 for (int i = 0; i < 24; ++i) {
2751 writer->write (j2c.data(), j2c.size());
2753 writer->finalize ();
2755 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2757 auto reel = make_shared<dcp::Reel>(
2758 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2759 make_shared<dcp::ReelSoundAsset>(ms, 0)
2762 reel->add (simple_markers());
2766 cpl->set_content_version (
2767 {"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"}
2769 cpl->set_annotation_text ("A Test DCP");
2770 cpl->set_issuer ("OpenDCP 0.0.25");
2771 cpl->set_creator ("OpenDCP 0.0.25");
2772 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2773 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2774 cpl->set_main_sound_sample_rate (48000);
2775 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2776 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2777 cpl->set_version_number (1);
2781 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2783 check_verify_result (
2786 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2791 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2793 vector<dcp::VerificationNote> notes;
2794 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"));
2795 auto reader = picture.start_read ();
2796 auto frame = reader->get_frame (0);
2797 verify_j2k (frame, notes);
2798 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2802 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2804 vector<dcp::VerificationNote> notes;
2805 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2806 auto reader = picture.start_read ();
2807 auto frame = reader->get_frame (0);
2808 verify_j2k (frame, notes);
2809 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2813 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2815 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2816 prepare_directory (dir);
2817 auto dcp = make_simple (dir);
2819 vector<dcp::VerificationNote> notes;
2820 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2821 auto reader = picture.start_read ();
2822 auto frame = reader->get_frame (0);
2823 verify_j2k (frame, notes);
2824 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2828 /** Check that ResourceID and the XML ID being different is spotted */
2829 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2831 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2832 prepare_directory (dir);
2834 ASDCP::WriterInfo writer_info;
2835 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2838 auto mxf_id = dcp::make_uuid ();
2839 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2840 BOOST_REQUIRE (c == Kumu::UUID_Length);
2842 auto resource_id = dcp::make_uuid ();
2843 ASDCP::TimedText::TimedTextDescriptor descriptor;
2844 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2845 DCP_ASSERT (c == Kumu::UUID_Length);
2847 auto xml_id = dcp::make_uuid ();
2848 ASDCP::TimedText::MXFWriter writer;
2849 auto subs_mxf = dir / "subs.mxf";
2850 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2851 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2852 writer.WriteTimedTextResource (dcp::String::compose(
2853 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2854 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2855 "<Id>urn:uuid:%1</Id>"
2856 "<ContentTitleText>Content</ContentTitleText>"
2857 "<AnnotationText>Annotation</AnnotationText>"
2858 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2859 "<ReelNumber>1</ReelNumber>"
2860 "<Language>en-US</Language>"
2861 "<EditRate>25 1</EditRate>"
2862 "<TimeCodeRate>25</TimeCodeRate>"
2863 "<StartTime>00:00:00:00</StartTime>"
2865 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2866 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2867 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2876 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2877 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2879 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2881 check_verify_result (
2884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2886 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2892 /** Check that ResourceID and the MXF ID being the same is spotted */
2893 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2895 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2896 prepare_directory (dir);
2898 ASDCP::WriterInfo writer_info;
2899 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2902 auto mxf_id = dcp::make_uuid ();
2903 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2904 BOOST_REQUIRE (c == Kumu::UUID_Length);
2906 auto resource_id = mxf_id;
2907 ASDCP::TimedText::TimedTextDescriptor descriptor;
2908 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2909 DCP_ASSERT (c == Kumu::UUID_Length);
2911 auto xml_id = resource_id;
2912 ASDCP::TimedText::MXFWriter writer;
2913 auto subs_mxf = dir / "subs.mxf";
2914 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2915 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2916 writer.WriteTimedTextResource (dcp::String::compose(
2917 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2918 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2919 "<Id>urn:uuid:%1</Id>"
2920 "<ContentTitleText>Content</ContentTitleText>"
2921 "<AnnotationText>Annotation</AnnotationText>"
2922 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2923 "<ReelNumber>1</ReelNumber>"
2924 "<Language>en-US</Language>"
2925 "<EditRate>25 1</EditRate>"
2926 "<TimeCodeRate>25</TimeCodeRate>"
2927 "<StartTime>00:00:00:00</StartTime>"
2929 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2930 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2931 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2940 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2941 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2943 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2945 check_verify_result (
2948 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2950 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2951 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }