2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_closed_caption_asset.h"
45 #include "reel_markers_asset.h"
46 #include "reel_mono_picture_asset.h"
47 #include "reel_sound_asset.h"
48 #include "reel_stereo_picture_asset.h"
49 #include "reel_subtitle_asset.h"
50 #include "smpte_subtitle_asset.h"
51 #include "stereo_picture_asset.h"
52 #include "stream_operators.h"
56 #include "verify_j2k.h"
57 #include <boost/test/unit_test.hpp>
58 #include <boost/algorithm/string.hpp>
67 using std::make_shared;
68 using boost::optional;
69 using namespace boost::filesystem;
70 using std::shared_ptr;
73 static list<pair<string, optional<path>>> stages;
74 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
75 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
76 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
77 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
78 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
79 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
80 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
83 stage (string s, optional<path> p)
85 stages.push_back (make_pair (s, p));
95 prepare_directory (path path)
97 using namespace boost::filesystem;
99 create_directories (path);
104 setup (int reference_number, string verify_test_suffix)
106 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
107 prepare_directory (dir);
108 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
109 copy_file (i.path(), dir / i.path().filename());
118 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
120 auto reel = make_shared<dcp::Reel>();
121 reel->add (reel_asset);
122 reel->add (simple_markers());
124 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
126 auto dcp = make_shared<dcp::DCP>(dir);
130 dcp::String::compose("libdcp %1", dcp::version),
131 dcp::String::compose("libdcp %1", dcp::version),
132 dcp::LocalTime().as_string(),
140 /** Class that can alter a file by searching and replacing strings within it.
141 * On destruction modifies the file whose name was given to the constructor.
149 _content = dcp::file_to_string (_path);
154 auto f = fopen(_path.string().c_str(), "w");
156 fwrite (_content.c_str(), _content.length(), 1, f);
160 void replace (string a, string b)
162 auto old_content = _content;
163 boost::algorithm::replace_all (_content, a, b);
164 BOOST_REQUIRE (_content != old_content);
167 void delete_lines (string from, string to)
169 vector<string> lines;
170 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
171 bool deleting = false;
172 auto old_content = _content;
174 for (auto i: lines) {
175 if (i.find(from) != string::npos) {
179 _content += i + "\n";
181 if (deleting && i.find(to) != string::npos) {
185 BOOST_REQUIRE (_content != old_content);
190 std::string _content;
197 dump_notes (vector<dcp::VerificationNote> const & notes)
199 for (auto i: notes) {
200 std::cout << dcp::note_to_string(i) << "\n";
208 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
210 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
211 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
212 for (auto i = 0U; i < notes.size(); ++i) {
213 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
220 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
222 auto dir = setup (1, suffix);
225 Editor e (file(suffix));
226 e.replace (from, to);
229 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
231 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
232 auto i = notes.begin();
233 auto j = codes.begin();
234 while (i != notes.end()) {
235 BOOST_CHECK_EQUAL (i->code(), *j);
242 BOOST_AUTO_TEST_CASE (verify_no_error)
245 auto dir = setup (1, "no_error");
246 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
248 path const cpl_file = dir / dcp_test1_cpl;
249 path const pkl_file = dir / dcp_test1_pkl;
250 path const assetmap_file = dir / "ASSETMAP.xml";
252 auto st = stages.begin();
253 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
254 BOOST_REQUIRE (st->second);
255 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
257 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
258 BOOST_REQUIRE (st->second);
259 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
261 BOOST_CHECK_EQUAL (st->first, "Checking reel");
262 BOOST_REQUIRE (!st->second);
264 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
265 BOOST_REQUIRE (st->second);
266 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
268 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
269 BOOST_REQUIRE (st->second);
270 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
272 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
273 BOOST_REQUIRE (st->second);
274 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
276 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
277 BOOST_REQUIRE (st->second);
278 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
280 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
281 BOOST_REQUIRE (st->second);
282 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
284 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
285 BOOST_REQUIRE (st->second);
286 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
288 BOOST_REQUIRE (st == stages.end());
290 BOOST_CHECK_EQUAL (notes.size(), 0);
294 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
296 using namespace boost::filesystem;
298 auto dir = setup (1, "incorrect_picture_sound_hash");
300 auto video_path = path(dir / "video.mxf");
301 auto mod = fopen(video_path.string().c_str(), "r+b");
303 fseek (mod, 4096, SEEK_SET);
305 fwrite (&x, sizeof(x), 1, mod);
308 auto audio_path = path(dir / "audio.mxf");
309 mod = fopen(audio_path.string().c_str(), "r+b");
311 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
312 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
315 dcp::ASDCPErrorSuspender sus;
316 check_verify_result (
319 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
320 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
325 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
327 using namespace boost::filesystem;
329 auto dir = setup (1, "mismatched_picture_sound_hashes");
332 Editor e (dir / dcp_test1_pkl);
333 e.replace ("<Hash>", "<Hash>x");
336 check_verify_result (
339 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xWU0/u1wM17y7Kriq06+65/ViX1o=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
349 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
351 auto dir = setup (1, "failed_read_content_kind");
354 Editor e (dir / dcp_test1_cpl);
355 e.replace ("<ContentKind>", "<ContentKind>x");
358 check_verify_result (
360 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
369 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
377 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
383 asset_map (string suffix)
385 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
389 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
391 check_verify_result_after_replace (
392 "invalid_picture_frame_rate", &cpl,
393 "<FrameRate>24 1", "<FrameRate>99 1",
394 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
395 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
399 BOOST_AUTO_TEST_CASE (verify_missing_asset)
401 auto dir = setup (1, "missing_asset");
402 remove (dir / "video.mxf");
403 check_verify_result (
406 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
411 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
413 check_verify_result_after_replace (
414 "empty_asset_path", &asset_map,
415 "<Path>video.mxf</Path>", "<Path></Path>",
416 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
421 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
423 check_verify_result_after_replace (
424 "mismatched_standard", &cpl,
425 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
426 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
427 dcp::VerificationNote::Code::INVALID_XML,
428 dcp::VerificationNote::Code::INVALID_XML,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
437 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
439 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
440 check_verify_result_after_replace (
441 "invalid_xml_cpl_id", &cpl,
442 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
443 { dcp::VerificationNote::Code::INVALID_XML }
448 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
450 check_verify_result_after_replace (
451 "invalid_xml_issue_date", &cpl,
452 "<IssueDate>", "<IssueDate>x",
453 { dcp::VerificationNote::Code::INVALID_XML,
454 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
459 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
461 check_verify_result_after_replace (
462 "invalid_xml_pkl_id", &pkl,
463 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
464 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
465 { dcp::VerificationNote::Code::INVALID_XML }
470 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
472 check_verify_result_after_replace (
473 "invalix_xml_asset_map_id", &asset_map,
474 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
475 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
476 { dcp::VerificationNote::Code::INVALID_XML }
481 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
484 auto dir = setup (3, "verify_invalid_standard");
485 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
487 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
488 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
489 path const assetmap_file = dir / "ASSETMAP";
491 auto st = stages.begin();
492 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
493 BOOST_REQUIRE (st->second);
494 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
496 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
497 BOOST_REQUIRE (st->second);
498 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
500 BOOST_CHECK_EQUAL (st->first, "Checking reel");
501 BOOST_REQUIRE (!st->second);
503 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
504 BOOST_REQUIRE (st->second);
505 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
507 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
508 BOOST_REQUIRE (st->second);
509 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
511 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
512 BOOST_REQUIRE (st->second);
513 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
515 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
516 BOOST_REQUIRE (st->second);
517 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
519 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
520 BOOST_REQUIRE (st->second);
521 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
523 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
524 BOOST_REQUIRE (st->second);
525 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
527 BOOST_REQUIRE (st == stages.end());
529 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
530 auto i = notes.begin ();
531 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
532 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
534 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
535 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
538 /* DCP with a short asset */
539 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
541 auto dir = setup (8, "invalid_duration");
542 check_verify_result (
545 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
546 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
547 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
548 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
549 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
550 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
557 dcp_from_frame (dcp::ArrayData const& frame, path dir)
559 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
560 create_directories (dir);
561 auto writer = asset->start_write (dir / "pic.mxf", true);
562 for (int i = 0; i < 24; ++i) {
563 writer->write (frame.data(), frame.size());
567 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
568 return write_dcp_with_single_asset (dir, reel_asset);
572 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
574 int const too_big = 1302083 * 2;
576 /* Compress a black image */
577 auto image = black_image ();
578 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
579 BOOST_REQUIRE (frame.size() < too_big);
581 /* Place it in a bigger block with some zero padding at the end */
582 dcp::ArrayData oversized_frame(too_big);
583 memcpy (oversized_frame.data(), frame.data(), frame.size());
584 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
586 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
587 prepare_directory (dir);
588 auto cpl = dcp_from_frame (oversized_frame, dir);
590 check_verify_result (
593 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
594 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
595 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
600 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
602 int const nearly_too_big = 1302083 * 0.98;
604 /* Compress a black image */
605 auto image = black_image ();
606 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
607 BOOST_REQUIRE (frame.size() < nearly_too_big);
609 /* Place it in a bigger block with some zero padding at the end */
610 dcp::ArrayData oversized_frame(nearly_too_big);
611 memcpy (oversized_frame.data(), frame.data(), frame.size());
612 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
614 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
615 prepare_directory (dir);
616 auto cpl = dcp_from_frame (oversized_frame, dir);
618 check_verify_result (
621 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
622 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
623 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
628 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
630 /* Compress a black image */
631 auto image = black_image ();
632 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
633 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
635 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
636 prepare_directory (dir);
637 auto cpl = dcp_from_frame (frame, dir);
639 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
643 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
645 path const dir("build/test/verify_valid_interop_subtitles");
646 prepare_directory (dir);
647 copy_file ("test/data/subs1.xml", dir / "subs.xml");
648 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
649 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
650 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
652 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
656 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
658 using namespace boost::filesystem;
660 path const dir("build/test/verify_invalid_interop_subtitles");
661 prepare_directory (dir);
662 copy_file ("test/data/subs1.xml", dir / "subs.xml");
663 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
664 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
665 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
668 Editor e (dir / "subs.xml");
669 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
672 check_verify_result (
675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
676 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
678 dcp::VerificationNote::Type::ERROR,
679 dcp::VerificationNote::Code::INVALID_XML,
680 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
688 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
690 path const dir("build/test/verify_valid_smpte_subtitles");
691 prepare_directory (dir);
692 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
693 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
694 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
695 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
697 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
701 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
703 using namespace boost::filesystem;
705 path const dir("build/test/verify_invalid_smpte_subtitles");
706 prepare_directory (dir);
707 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
708 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
709 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
710 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
712 check_verify_result (
715 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
717 dcp::VerificationNote::Type::ERROR,
718 dcp::VerificationNote::Code::INVALID_XML,
719 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
723 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
724 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
729 BOOST_AUTO_TEST_CASE (verify_external_asset)
731 path const ov_dir("build/test/verify_external_asset");
732 prepare_directory (ov_dir);
734 auto image = black_image ();
735 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
736 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
737 dcp_from_frame (frame, ov_dir);
739 dcp::DCP ov (ov_dir);
742 path const vf_dir("build/test/verify_external_asset_vf");
743 prepare_directory (vf_dir);
745 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
746 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
748 check_verify_result (
751 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
752 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
757 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
759 path const dir("build/test/verify_valid_cpl_metadata");
760 prepare_directory (dir);
762 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
763 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
764 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
766 auto reel = make_shared<dcp::Reel>();
767 reel->add (reel_asset);
769 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
770 reel->add (simple_markers(16 * 24));
772 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
774 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
775 cpl->set_main_sound_sample_rate (48000);
776 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
777 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
778 cpl->set_version_number (1);
783 dcp::Standard::SMPTE,
784 dcp::String::compose("libdcp %1", dcp::version),
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::LocalTime().as_string(),
792 path find_cpl (path dir)
794 for (auto i: directory_iterator(dir)) {
795 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
800 BOOST_REQUIRE (false);
805 /* DCP with invalid CompositionMetadataAsset */
806 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
808 using namespace boost::filesystem;
810 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
811 prepare_directory (dir);
813 auto reel = make_shared<dcp::Reel>();
814 reel->add (black_picture_asset(dir));
815 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
817 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
818 cpl->set_main_sound_sample_rate (48000);
819 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
820 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
821 cpl->set_version_number (1);
823 reel->add (simple_markers());
828 dcp::Standard::SMPTE,
829 dcp::String::compose("libdcp %1", dcp::version),
830 dcp::String::compose("libdcp %1", dcp::version),
831 dcp::LocalTime().as_string(),
836 Editor e (find_cpl(dir));
837 e.replace ("MainSound", "MainSoundX");
840 check_verify_result (
843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
846 dcp::VerificationNote::Type::ERROR,
847 dcp::VerificationNote::Code::INVALID_XML,
848 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
849 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
850 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
851 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
852 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
853 "ExtensionMetadataList?,)'"),
854 canonical(cpl->file().get()),
857 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
862 /* DCP with invalid CompositionMetadataAsset */
863 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
865 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
866 prepare_directory (dir);
868 auto reel = make_shared<dcp::Reel>();
869 reel->add (black_picture_asset(dir));
870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
872 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
873 cpl->set_main_sound_sample_rate (48000);
874 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
875 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
880 dcp::Standard::SMPTE,
881 dcp::String::compose("libdcp %1", dcp::version),
882 dcp::String::compose("libdcp %1", dcp::version),
883 dcp::LocalTime().as_string(),
888 Editor e (find_cpl(dir));
889 e.replace ("meta:Width", "meta:WidthX");
892 check_verify_result (
894 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
899 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
901 path const dir("build/test/verify_invalid_language1");
902 prepare_directory (dir);
903 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
904 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
905 asset->_language = "wrong-andbad";
906 asset->write (dir / "subs.mxf");
907 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
908 reel_asset->_language = "badlang";
909 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
911 check_verify_result (
914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
921 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
922 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
924 path const dir("build/test/verify_invalid_language2");
925 prepare_directory (dir);
926 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
927 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
928 asset->_language = "wrong-andbad";
929 asset->write (dir / "subs.mxf");
930 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
931 reel_asset->_language = "badlang";
932 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
934 check_verify_result (
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
944 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
945 * the release territory.
947 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
949 path const dir("build/test/verify_invalid_language3");
950 prepare_directory (dir);
952 auto picture = simple_picture (dir, "foo");
953 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
954 auto reel = make_shared<dcp::Reel>();
955 reel->add (reel_picture);
956 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
957 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
958 reel->add (reel_sound);
959 reel->add (simple_markers());
961 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
963 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
964 cpl->_additional_subtitle_languages.push_back("andso-is-this");
965 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
966 cpl->set_main_sound_sample_rate (48000);
967 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
968 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
969 cpl->set_version_number (1);
970 cpl->_release_territory = "fred-jim";
971 auto dcp = make_shared<dcp::DCP>(dir);
974 dcp::Standard::SMPTE,
975 dcp::String::compose("libdcp %1", dcp::version),
976 dcp::String::compose("libdcp %1", dcp::version),
977 dcp::LocalTime().as_string(),
981 check_verify_result (
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
993 vector<dcp::VerificationNote>
994 check_picture_size (int width, int height, int frame_rate, bool three_d)
996 using namespace boost::filesystem;
998 path dcp_path = "build/test/verify_picture_test";
999 prepare_directory (dcp_path);
1001 shared_ptr<dcp::PictureAsset> mp;
1003 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1005 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1007 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1009 auto image = black_image (dcp::Size(width, height));
1010 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1011 int const length = three_d ? frame_rate * 2 : frame_rate;
1012 for (int i = 0; i < length; ++i) {
1013 picture_writer->write (j2c.data(), j2c.size());
1015 picture_writer->finalize ();
1017 auto d = make_shared<dcp::DCP>(dcp_path);
1018 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1019 cpl->set_annotation_text ("A Test DCP");
1020 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1021 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1022 cpl->set_main_sound_sample_rate (48000);
1023 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1024 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1025 cpl->set_version_number (1);
1027 auto reel = make_shared<dcp::Reel>();
1030 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1032 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1035 reel->add (simple_markers(frame_rate));
1041 dcp::Standard::SMPTE,
1042 dcp::String::compose("libdcp %1", dcp::version),
1043 dcp::String::compose("libdcp %1", dcp::version),
1044 dcp::LocalTime().as_string(),
1048 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1054 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1056 auto notes = check_picture_size(width, height, frame_rate, three_d);
1057 BOOST_CHECK_EQUAL (notes.size(), 0U);
1063 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1065 auto notes = check_picture_size(width, height, frame_rate, three_d);
1066 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1067 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1068 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1074 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1076 auto notes = check_picture_size(width, height, frame_rate, three_d);
1077 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1078 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1079 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1085 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1087 auto notes = check_picture_size(width, height, frame_rate, three_d);
1088 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1089 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1090 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1094 BOOST_AUTO_TEST_CASE (verify_picture_size)
1096 using namespace boost::filesystem;
1099 check_picture_size_ok (2048, 858, 24, false);
1100 check_picture_size_ok (2048, 858, 25, false);
1101 check_picture_size_ok (2048, 858, 48, false);
1102 check_picture_size_ok (2048, 858, 24, true);
1103 check_picture_size_ok (2048, 858, 25, true);
1104 check_picture_size_ok (2048, 858, 48, true);
1107 check_picture_size_ok (1998, 1080, 24, false);
1108 check_picture_size_ok (1998, 1080, 25, false);
1109 check_picture_size_ok (1998, 1080, 48, false);
1110 check_picture_size_ok (1998, 1080, 24, true);
1111 check_picture_size_ok (1998, 1080, 25, true);
1112 check_picture_size_ok (1998, 1080, 48, true);
1115 check_picture_size_ok (4096, 1716, 24, false);
1118 check_picture_size_ok (3996, 2160, 24, false);
1120 /* Bad frame size */
1121 check_picture_size_bad_frame_size (2050, 858, 24, false);
1122 check_picture_size_bad_frame_size (2048, 658, 25, false);
1123 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1124 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1126 /* Bad 2K frame rate */
1127 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1128 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1129 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1131 /* Bad 4K frame rate */
1132 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1133 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1136 auto notes = check_picture_size(3996, 2160, 24, true);
1137 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1138 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1139 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1145 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1148 make_shared<dcp::SubtitleString>(
1156 dcp::Time(start_frame, 24, 24),
1157 dcp::Time(end_frame, 24, 24),
1159 dcp::HAlign::CENTER,
1161 dcp::VAlign::CENTER,
1162 dcp::Direction::LTR,
1173 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1175 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1176 prepare_directory (dir);
1178 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1179 for (int i = 0; i < 2048; ++i) {
1180 add_test_subtitle (asset, i * 24, i * 24 + 20);
1182 asset->set_language (dcp::LanguageTag("de-DE"));
1183 asset->write (dir / "subs.mxf");
1184 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1185 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1187 check_verify_result (
1190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1192 dcp::VerificationNote::Type::BV21_ERROR,
1193 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1195 canonical(dir / "subs.mxf")
1197 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1198 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1204 shared_ptr<dcp::SMPTESubtitleAsset>
1205 make_large_subtitle_asset (path font_file)
1207 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1208 dcp::ArrayData big_fake_font(1024 * 1024);
1209 big_fake_font.write (font_file);
1210 for (int i = 0; i < 116; ++i) {
1211 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1219 verify_timed_text_asset_too_large (string name)
1221 auto const dir = path("build/test") / name;
1222 prepare_directory (dir);
1223 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1224 add_test_subtitle (asset, 0, 240);
1225 asset->set_language (dcp::LanguageTag("de-DE"));
1226 asset->write (dir / "subs.mxf");
1228 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1229 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1231 check_verify_result (
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1237 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1238 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1243 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1245 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1246 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1250 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1252 path dir = "build/test/verify_missing_subtitle_language";
1253 prepare_directory (dir);
1254 auto dcp = make_simple (dir, 1, 106);
1257 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1258 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1259 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1260 "<ContentTitleText>Content</ContentTitleText>"
1261 "<AnnotationText>Annotation</AnnotationText>"
1262 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1263 "<ReelNumber>1</ReelNumber>"
1264 "<EditRate>24 1</EditRate>"
1265 "<TimeCodeRate>24</TimeCodeRate>"
1266 "<StartTime>00:00:00:00</StartTime>"
1267 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1269 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1270 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1271 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1277 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1278 BOOST_REQUIRE (xml_file);
1279 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1281 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1282 subs->write (dir / "subs.mxf");
1284 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1285 dcp->cpls().front()->reels().front()->add(reel_subs);
1287 dcp::Standard::SMPTE,
1288 dcp::String::compose("libdcp %1", dcp::version),
1289 dcp::String::compose("libdcp %1", dcp::version),
1290 dcp::LocalTime().as_string(),
1294 check_verify_result (
1297 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1298 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1303 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1305 path path ("build/test/verify_mismatched_subtitle_languages");
1306 auto constexpr reel_length = 192;
1307 auto dcp = make_simple (path, 2, reel_length);
1308 auto cpl = dcp->cpls()[0];
1311 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1312 subs->set_language (dcp::LanguageTag("de-DE"));
1313 subs->add (simple_subtitle());
1314 subs->write (path / "subs1.mxf");
1315 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1316 cpl->reels()[0]->add(reel_subs);
1320 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1321 subs->set_language (dcp::LanguageTag("en-US"));
1322 subs->add (simple_subtitle());
1323 subs->write (path / "subs2.mxf");
1324 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1325 cpl->reels()[1]->add(reel_subs);
1329 dcp::Standard::SMPTE,
1330 dcp::String::compose("libdcp %1", dcp::version),
1331 dcp::String::compose("libdcp %1", dcp::version),
1332 dcp::LocalTime().as_string(),
1336 check_verify_result (
1339 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1346 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1348 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1349 auto constexpr reel_length = 192;
1350 auto dcp = make_simple (path, 2, reel_length);
1351 auto cpl = dcp->cpls()[0];
1354 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1355 ccaps->set_language (dcp::LanguageTag("de-DE"));
1356 ccaps->add (simple_subtitle());
1357 ccaps->write (path / "subs1.mxf");
1358 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1359 cpl->reels()[0]->add(reel_ccaps);
1363 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1364 ccaps->set_language (dcp::LanguageTag("en-US"));
1365 ccaps->add (simple_subtitle());
1366 ccaps->write (path / "subs2.mxf");
1367 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1368 cpl->reels()[1]->add(reel_ccaps);
1372 dcp::Standard::SMPTE,
1373 dcp::String::compose("libdcp %1", dcp::version),
1374 dcp::String::compose("libdcp %1", dcp::version),
1375 dcp::LocalTime().as_string(),
1379 check_verify_result (
1382 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1383 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1388 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1390 path dir = "build/test/verify_missing_subtitle_start_time";
1391 prepare_directory (dir);
1392 auto dcp = make_simple (dir, 1, 106);
1395 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1396 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1397 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1398 "<ContentTitleText>Content</ContentTitleText>"
1399 "<AnnotationText>Annotation</AnnotationText>"
1400 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1401 "<ReelNumber>1</ReelNumber>"
1402 "<Language>de-DE</Language>"
1403 "<EditRate>24 1</EditRate>"
1404 "<TimeCodeRate>24</TimeCodeRate>"
1405 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1407 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1408 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1409 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1415 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1416 BOOST_REQUIRE (xml_file);
1417 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1419 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1420 subs->write (dir / "subs.mxf");
1422 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1423 dcp->cpls().front()->reels().front()->add(reel_subs);
1425 dcp::Standard::SMPTE,
1426 dcp::String::compose("libdcp %1", dcp::version),
1427 dcp::String::compose("libdcp %1", dcp::version),
1428 dcp::LocalTime().as_string(),
1432 check_verify_result (
1435 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1436 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1441 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1443 path dir = "build/test/verify_invalid_subtitle_start_time";
1444 prepare_directory (dir);
1445 auto dcp = make_simple (dir, 1, 106);
1448 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1449 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1450 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1451 "<ContentTitleText>Content</ContentTitleText>"
1452 "<AnnotationText>Annotation</AnnotationText>"
1453 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1454 "<ReelNumber>1</ReelNumber>"
1455 "<Language>de-DE</Language>"
1456 "<EditRate>24 1</EditRate>"
1457 "<TimeCodeRate>24</TimeCodeRate>"
1458 "<StartTime>00:00:02:00</StartTime>"
1459 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1461 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1462 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1463 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1469 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1470 BOOST_REQUIRE (xml_file);
1471 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1473 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1474 subs->write (dir / "subs.mxf");
1476 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1477 dcp->cpls().front()->reels().front()->add(reel_subs);
1479 dcp::Standard::SMPTE,
1480 dcp::String::compose("libdcp %1", dcp::version),
1481 dcp::String::compose("libdcp %1", dcp::version),
1482 dcp::LocalTime().as_string(),
1486 check_verify_result (
1489 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1490 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1498 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1501 , v_position(v_position_)
1513 shared_ptr<dcp::CPL>
1514 dcp_with_text (path dir, vector<TestText> subs)
1516 prepare_directory (dir);
1517 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1518 asset->set_start_time (dcp::Time());
1519 for (auto i: subs) {
1520 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1522 asset->set_language (dcp::LanguageTag("de-DE"));
1523 asset->write (dir / "subs.mxf");
1525 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1526 return write_dcp_with_single_asset (dir, reel_asset);
1530 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1532 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1533 /* Just too early */
1534 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1535 check_verify_result (
1538 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1539 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1545 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1547 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1548 /* Just late enough */
1549 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1550 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1554 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1556 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1557 prepare_directory (dir);
1559 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1560 asset1->set_start_time (dcp::Time());
1561 /* Just late enough */
1562 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1563 asset1->set_language (dcp::LanguageTag("de-DE"));
1564 asset1->write (dir / "subs1.mxf");
1565 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1566 auto reel1 = make_shared<dcp::Reel>();
1567 reel1->add (reel_asset1);
1568 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1569 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1570 reel1->add (markers1);
1572 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1573 asset2->set_start_time (dcp::Time());
1574 /* This would be too early on first reel but should be OK on the second */
1575 add_test_subtitle (asset2, 3, 4 * 24);
1576 asset2->set_language (dcp::LanguageTag("de-DE"));
1577 asset2->write (dir / "subs2.mxf");
1578 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1579 auto reel2 = make_shared<dcp::Reel>();
1580 reel2->add (reel_asset2);
1581 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1582 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1583 reel2->add (markers2);
1585 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1588 auto dcp = make_shared<dcp::DCP>(dir);
1591 dcp::Standard::SMPTE,
1592 dcp::String::compose("libdcp %1", dcp::version),
1593 dcp::String::compose("libdcp %1", dcp::version),
1594 dcp::LocalTime().as_string(),
1599 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1603 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1605 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1606 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1610 { 5 * 24 + 1, 6 * 24 },
1612 check_verify_result (
1615 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1616 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1621 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1623 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1624 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1628 { 5 * 24 + 16, 8 * 24 },
1630 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1634 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1636 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1637 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1638 check_verify_result (
1641 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1642 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1647 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1649 auto const dir = path("build/test/verify_valid_subtitle_duration");
1650 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1651 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1655 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1657 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1658 prepare_directory (dir);
1659 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1660 asset->set_start_time (dcp::Time());
1661 add_test_subtitle (asset, 0, 4 * 24);
1662 asset->set_language (dcp::LanguageTag("de-DE"));
1663 asset->write (dir / "subs.mxf");
1665 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1666 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1667 check_verify_result (
1670 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1671 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1672 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1673 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1679 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1681 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1682 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1685 { 96, 200, 0.0, "We" },
1686 { 96, 200, 0.1, "have" },
1687 { 96, 200, 0.2, "four" },
1688 { 96, 200, 0.3, "lines" }
1690 check_verify_result (
1693 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1694 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1699 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1701 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1702 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1705 { 96, 200, 0.0, "We" },
1706 { 96, 200, 0.1, "have" },
1707 { 96, 200, 0.2, "four" },
1709 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1713 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1715 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1716 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1719 { 96, 300, 0.0, "We" },
1720 { 96, 300, 0.1, "have" },
1721 { 150, 180, 0.2, "four" },
1722 { 150, 180, 0.3, "lines" }
1724 check_verify_result (
1727 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1733 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1735 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1736 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1739 { 96, 300, 0.0, "We" },
1740 { 96, 300, 0.1, "have" },
1741 { 150, 180, 0.2, "four" },
1742 { 190, 250, 0.3, "lines" }
1744 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1748 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1750 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1751 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1754 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1756 check_verify_result (
1759 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1760 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1765 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1767 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1768 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1771 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1773 check_verify_result (
1776 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1777 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1782 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1784 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1785 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1788 { 96, 200, 0.0, "We" },
1789 { 96, 200, 0.1, "have" },
1790 { 96, 200, 0.2, "four" },
1791 { 96, 200, 0.3, "lines" }
1793 check_verify_result (
1796 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1797 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1802 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1804 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1805 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1808 { 96, 200, 0.0, "We" },
1809 { 96, 200, 0.1, "have" },
1810 { 96, 200, 0.2, "four" },
1812 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1816 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1818 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1819 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1822 { 96, 300, 0.0, "We" },
1823 { 96, 300, 0.1, "have" },
1824 { 150, 180, 0.2, "four" },
1825 { 150, 180, 0.3, "lines" }
1827 check_verify_result (
1830 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1831 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1836 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1838 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1839 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1842 { 96, 300, 0.0, "We" },
1843 { 96, 300, 0.1, "have" },
1844 { 150, 180, 0.2, "four" },
1845 { 190, 250, 0.3, "lines" }
1847 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1851 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1853 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1854 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1857 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1859 check_verify_result (
1862 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1863 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1868 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1870 path const dir("build/test/verify_invalid_sound_frame_rate");
1871 prepare_directory (dir);
1873 auto picture = simple_picture (dir, "foo");
1874 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1875 auto reel = make_shared<dcp::Reel>();
1876 reel->add (reel_picture);
1877 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1878 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1879 reel->add (reel_sound);
1880 reel->add (simple_markers());
1881 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1883 auto dcp = make_shared<dcp::DCP>(dir);
1886 dcp::Standard::SMPTE,
1887 dcp::String::compose("libdcp %1", dcp::version),
1888 dcp::String::compose("libdcp %1", dcp::version),
1889 dcp::LocalTime().as_string(),
1893 check_verify_result (
1896 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1902 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1904 path const dir("build/test/verify_missing_cpl_annotation_text");
1905 auto dcp = make_simple (dir);
1907 dcp::Standard::SMPTE,
1908 dcp::String::compose("libdcp %1", dcp::version),
1909 dcp::String::compose("libdcp %1", dcp::version),
1910 dcp::LocalTime().as_string(),
1914 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1916 auto const cpl = dcp->cpls()[0];
1919 BOOST_REQUIRE (cpl->file());
1920 Editor e(cpl->file().get());
1921 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1924 check_verify_result (
1927 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1928 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1933 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1935 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1936 auto dcp = make_simple (dir);
1938 dcp::Standard::SMPTE,
1939 dcp::String::compose("libdcp %1", dcp::version),
1940 dcp::String::compose("libdcp %1", dcp::version),
1941 dcp::LocalTime().as_string(),
1945 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1946 auto const cpl = dcp->cpls()[0];
1949 BOOST_REQUIRE (cpl->file());
1950 Editor e(cpl->file().get());
1951 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1954 check_verify_result (
1957 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1958 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1963 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1965 path const dir("build/test/verify_mismatched_asset_duration");
1966 prepare_directory (dir);
1967 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1968 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1970 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1971 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1973 auto reel = make_shared<dcp::Reel>(
1974 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1975 make_shared<dcp::ReelSoundAsset>(ms, 0)
1978 reel->add (simple_markers());
1983 dcp::Standard::SMPTE,
1984 dcp::String::compose("libdcp %1", dcp::version),
1985 dcp::String::compose("libdcp %1", dcp::version),
1986 dcp::LocalTime().as_string(),
1990 check_verify_result (
1993 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1994 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2001 shared_ptr<dcp::CPL>
2002 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2004 prepare_directory (dir);
2005 auto dcp = make_shared<dcp::DCP>(dir);
2006 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2008 auto constexpr reel_length = 192;
2010 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2011 subs->set_language (dcp::LanguageTag("de-DE"));
2012 subs->set_start_time (dcp::Time());
2013 subs->add (simple_subtitle());
2014 subs->write (dir / "subs.mxf");
2015 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2017 auto reel1 = make_shared<dcp::Reel>(
2018 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2019 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2023 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2026 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2027 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2028 reel1->add (markers1);
2032 auto reel2 = make_shared<dcp::Reel>(
2033 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2034 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2038 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2041 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2042 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2043 reel2->add (markers2);
2049 dcp::Standard::SMPTE,
2050 dcp::String::compose("libdcp %1", dcp::version),
2051 dcp::String::compose("libdcp %1", dcp::version),
2052 dcp::LocalTime().as_string(),
2060 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2063 path dir ("build/test/missing_main_subtitle_from_some_reels");
2064 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2065 check_verify_result (
2068 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2075 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2076 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2077 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2081 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2082 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2083 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2089 shared_ptr<dcp::CPL>
2090 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2092 prepare_directory (dir);
2093 auto dcp = make_shared<dcp::DCP>(dir);
2094 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2096 auto constexpr reel_length = 192;
2098 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2099 subs->set_language (dcp::LanguageTag("de-DE"));
2100 subs->set_start_time (dcp::Time());
2101 subs->add (simple_subtitle());
2102 subs->write (dir / "subs.mxf");
2104 auto reel1 = make_shared<dcp::Reel>(
2105 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2106 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2109 for (int i = 0; i < caps_in_reel1; ++i) {
2110 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2113 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2114 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2115 reel1->add (markers1);
2119 auto reel2 = make_shared<dcp::Reel>(
2120 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2121 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2124 for (int i = 0; i < caps_in_reel2; ++i) {
2125 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2128 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2129 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2130 reel2->add (markers2);
2136 dcp::Standard::SMPTE,
2137 dcp::String::compose("libdcp %1", dcp::version),
2138 dcp::String::compose("libdcp %1", dcp::version),
2139 dcp::LocalTime().as_string(),
2147 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2150 path dir ("build/test/mismatched_closed_caption_asset_counts");
2151 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2152 check_verify_result (
2155 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2161 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2162 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2163 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2167 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2168 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2169 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2176 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2178 prepare_directory (dir);
2179 auto dcp = make_shared<dcp::DCP>(dir);
2180 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2182 auto constexpr reel_length = 192;
2184 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2185 subs->set_language (dcp::LanguageTag("de-DE"));
2186 subs->set_start_time (dcp::Time());
2187 subs->add (simple_subtitle());
2188 subs->write (dir / "subs.mxf");
2189 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2192 auto reel = make_shared<dcp::Reel>(
2193 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2194 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2197 reel->add (reel_text);
2199 reel->add (simple_markers(reel_length));
2205 dcp::Standard::SMPTE,
2206 dcp::String::compose("libdcp %1", dcp::version),
2207 dcp::String::compose("libdcp %1", dcp::version),
2208 dcp::LocalTime().as_string(),
2212 check_verify_result (
2215 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2216 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2221 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2223 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2224 "build/test/verify_subtitle_entry_point_must_be_present",
2225 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2226 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2227 asset->unset_entry_point ();
2231 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2232 "build/test/verify_subtitle_entry_point_must_be_zero",
2233 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2234 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2235 asset->set_entry_point (4);
2239 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2240 "build/test/verify_closed_caption_entry_point_must_be_present",
2241 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2242 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2243 asset->unset_entry_point ();
2247 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2248 "build/test/verify_closed_caption_entry_point_must_be_zero",
2249 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2250 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2251 asset->set_entry_point (9);
2257 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2261 path const dir("build/test/verify_missing_hash");
2262 auto dcp = make_simple (dir);
2264 dcp::Standard::SMPTE,
2265 dcp::String::compose("libdcp %1", dcp::version),
2266 dcp::String::compose("libdcp %1", dcp::version),
2267 dcp::LocalTime().as_string(),
2271 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2272 auto const cpl = dcp->cpls()[0];
2275 BOOST_REQUIRE (cpl->file());
2276 Editor e(cpl->file().get());
2277 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2280 check_verify_result (
2283 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2284 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2291 verify_markers_test (
2293 vector<pair<dcp::Marker, dcp::Time>> markers,
2294 vector<dcp::VerificationNote> test_notes
2297 auto dcp = make_simple (dir);
2298 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2299 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2300 for (auto const& i: markers) {
2301 markers_asset->set (i.first, i.second);
2303 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2305 dcp::Standard::SMPTE,
2306 dcp::String::compose("libdcp %1", dcp::version),
2307 dcp::String::compose("libdcp %1", dcp::version),
2308 dcp::LocalTime().as_string(),
2312 check_verify_result ({dir}, test_notes);
2316 BOOST_AUTO_TEST_CASE (verify_markers)
2318 verify_markers_test (
2319 "build/test/verify_markers_all_correct",
2321 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2322 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2323 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2324 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2329 verify_markers_test (
2330 "build/test/verify_markers_missing_ffec",
2332 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2333 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2334 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2340 verify_markers_test (
2341 "build/test/verify_markers_missing_ffmc",
2343 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2344 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2345 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2348 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2351 verify_markers_test (
2352 "build/test/verify_markers_missing_ffoc",
2354 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2355 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2356 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2359 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2362 verify_markers_test (
2363 "build/test/verify_markers_missing_lfoc",
2365 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2366 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2367 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2370 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2373 verify_markers_test (
2374 "build/test/verify_markers_incorrect_ffoc",
2376 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2377 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2378 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2379 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2382 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2385 verify_markers_test (
2386 "build/test/verify_markers_incorrect_lfoc",
2388 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2389 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2390 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2391 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2394 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2399 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2401 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2402 prepare_directory (dir);
2403 auto dcp = make_simple (dir);
2404 auto cpl = dcp->cpls()[0];
2405 cpl->unset_version_number();
2407 dcp::Standard::SMPTE,
2408 dcp::String::compose("libdcp %1", dcp::version),
2409 dcp::String::compose("libdcp %1", dcp::version),
2410 dcp::LocalTime().as_string(),
2414 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2418 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2420 path dir = "build/test/verify_missing_extension_metadata1";
2421 auto dcp = make_simple (dir);
2423 dcp::Standard::SMPTE,
2424 dcp::String::compose("libdcp %1", dcp::version),
2425 dcp::String::compose("libdcp %1", dcp::version),
2426 dcp::LocalTime().as_string(),
2430 auto cpl = dcp->cpls()[0];
2433 Editor e (cpl->file().get());
2434 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2437 check_verify_result (
2440 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2441 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2446 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2448 path dir = "build/test/verify_missing_extension_metadata2";
2449 auto dcp = make_simple (dir);
2451 dcp::Standard::SMPTE,
2452 dcp::String::compose("libdcp %1", dcp::version),
2453 dcp::String::compose("libdcp %1", dcp::version),
2454 dcp::LocalTime().as_string(),
2458 auto cpl = dcp->cpls()[0];
2461 Editor e (cpl->file().get());
2462 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2465 check_verify_result (
2468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2474 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2476 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2477 auto dcp = make_simple (dir);
2479 dcp::Standard::SMPTE,
2480 dcp::String::compose("libdcp %1", dcp::version),
2481 dcp::String::compose("libdcp %1", dcp::version),
2482 dcp::LocalTime().as_string(),
2486 auto const cpl = dcp->cpls()[0];
2489 Editor e (cpl->file().get());
2490 e.replace ("<meta:Name>A", "<meta:NameX>A");
2491 e.replace ("n</meta:Name>", "n</meta:NameX>");
2494 check_verify_result (
2497 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2498 { 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 },
2499 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2504 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2506 path dir = "build/test/verify_invalid_extension_metadata1";
2507 auto dcp = make_simple (dir);
2509 dcp::Standard::SMPTE,
2510 dcp::String::compose("libdcp %1", dcp::version),
2511 dcp::String::compose("libdcp %1", dcp::version),
2512 dcp::LocalTime().as_string(),
2516 auto cpl = dcp->cpls()[0];
2519 Editor e (cpl->file().get());
2520 e.replace ("Application", "Fred");
2523 check_verify_result (
2526 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2527 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2532 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2534 path dir = "build/test/verify_invalid_extension_metadata2";
2535 auto dcp = make_simple (dir);
2537 dcp::Standard::SMPTE,
2538 dcp::String::compose("libdcp %1", dcp::version),
2539 dcp::String::compose("libdcp %1", dcp::version),
2540 dcp::LocalTime().as_string(),
2544 auto cpl = dcp->cpls()[0];
2547 Editor e (cpl->file().get());
2548 e.replace ("DCP Constraints Profile", "Fred");
2551 check_verify_result (
2554 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2555 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2560 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2562 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2563 auto dcp = make_simple (dir);
2565 dcp::Standard::SMPTE,
2566 dcp::String::compose("libdcp %1", dcp::version),
2567 dcp::String::compose("libdcp %1", dcp::version),
2568 dcp::LocalTime().as_string(),
2572 auto const cpl = dcp->cpls()[0];
2575 Editor e (cpl->file().get());
2576 e.replace ("<meta:Value>", "<meta:ValueX>");
2577 e.replace ("</meta:Value>", "</meta:ValueX>");
2580 check_verify_result (
2583 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2584 { 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 },
2585 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2590 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2592 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2593 auto dcp = make_simple (dir);
2595 dcp::Standard::SMPTE,
2596 dcp::String::compose("libdcp %1", dcp::version),
2597 dcp::String::compose("libdcp %1", dcp::version),
2598 dcp::LocalTime().as_string(),
2602 auto const cpl = dcp->cpls()[0];
2605 Editor e (cpl->file().get());
2606 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2609 check_verify_result (
2612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2613 { 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() },
2618 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2620 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2621 auto dcp = make_simple (dir);
2623 dcp::Standard::SMPTE,
2624 dcp::String::compose("libdcp %1", dcp::version),
2625 dcp::String::compose("libdcp %1", dcp::version),
2626 dcp::LocalTime().as_string(),
2630 auto const cpl = dcp->cpls()[0];
2633 Editor e (cpl->file().get());
2634 e.replace ("<meta:Property>", "<meta:PropertyX>");
2635 e.replace ("</meta:Property>", "</meta:PropertyX>");
2638 check_verify_result (
2641 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2642 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2643 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2648 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2650 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2651 auto dcp = make_simple (dir);
2653 dcp::Standard::SMPTE,
2654 dcp::String::compose("libdcp %1", dcp::version),
2655 dcp::String::compose("libdcp %1", dcp::version),
2656 dcp::LocalTime().as_string(),
2660 auto const cpl = dcp->cpls()[0];
2663 Editor e (cpl->file().get());
2664 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2665 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2668 check_verify_result (
2671 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2672 { 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 },
2673 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2679 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2681 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2682 prepare_directory (dir);
2683 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2684 copy_file (i.path(), dir / i.path().filename());
2687 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2688 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2692 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2695 check_verify_result (
2698 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2699 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2701 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2702 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2703 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2704 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2710 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2712 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2713 prepare_directory (dir);
2714 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2715 copy_file (i.path(), dir / i.path().filename());
2718 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2719 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2722 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2725 check_verify_result (
2728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2730 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2731 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2732 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2733 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2734 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2739 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2741 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2742 prepare_directory (dir);
2743 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2744 copy_file (i.path(), dir / i.path().filename());
2748 Editor e (dir / dcp_test1_pkl);
2749 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2752 check_verify_result ({dir}, {});
2756 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2758 path dir ("build/test/verify_must_not_be_partially_encrypted");
2759 prepare_directory (dir);
2763 auto signer = make_shared<dcp::CertificateChain>();
2764 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2765 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2766 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2767 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2769 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2773 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2776 auto writer = mp->start_write (dir / "video.mxf", false);
2777 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2778 for (int i = 0; i < 24; ++i) {
2779 writer->write (j2c.data(), j2c.size());
2781 writer->finalize ();
2783 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2785 auto reel = make_shared<dcp::Reel>(
2786 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2787 make_shared<dcp::ReelSoundAsset>(ms, 0)
2790 reel->add (simple_markers());
2794 cpl->set_content_version (
2795 {"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"}
2797 cpl->set_annotation_text ("A Test DCP");
2798 cpl->set_issuer ("OpenDCP 0.0.25");
2799 cpl->set_creator ("OpenDCP 0.0.25");
2800 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2801 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2802 cpl->set_main_sound_sample_rate (48000);
2803 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2804 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2805 cpl->set_version_number (1);
2809 d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2811 check_verify_result (
2814 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2819 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2821 vector<dcp::VerificationNote> notes;
2822 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"));
2823 auto reader = picture.start_read ();
2824 auto frame = reader->get_frame (0);
2825 verify_j2k (frame, notes);
2826 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2830 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2832 vector<dcp::VerificationNote> notes;
2833 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2834 auto reader = picture.start_read ();
2835 auto frame = reader->get_frame (0);
2836 verify_j2k (frame, notes);
2837 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2841 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2843 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2844 prepare_directory (dir);
2845 auto dcp = make_simple (dir);
2846 dcp->write_xml (dcp::Standard::SMPTE);
2847 vector<dcp::VerificationNote> notes;
2848 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2849 auto reader = picture.start_read ();
2850 auto frame = reader->get_frame (0);
2851 verify_j2k (frame, notes);
2852 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2856 /** Check that ResourceID and the XML ID being different is spotted */
2857 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2859 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2860 prepare_directory (dir);
2862 ASDCP::WriterInfo writer_info;
2863 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2866 auto mxf_id = dcp::make_uuid ();
2867 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2868 BOOST_REQUIRE (c == Kumu::UUID_Length);
2870 auto resource_id = dcp::make_uuid ();
2871 ASDCP::TimedText::TimedTextDescriptor descriptor;
2872 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2873 DCP_ASSERT (c == Kumu::UUID_Length);
2875 auto xml_id = dcp::make_uuid ();
2876 ASDCP::TimedText::MXFWriter writer;
2877 auto subs_mxf = dir / "subs.mxf";
2878 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2879 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2880 writer.WriteTimedTextResource (dcp::String::compose(
2881 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2882 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2883 "<Id>urn:uuid:%1</Id>"
2884 "<ContentTitleText>Content</ContentTitleText>"
2885 "<AnnotationText>Annotation</AnnotationText>"
2886 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2887 "<ReelNumber>1</ReelNumber>"
2888 "<Language>en-US</Language>"
2889 "<EditRate>25 1</EditRate>"
2890 "<TimeCodeRate>25</TimeCodeRate>"
2891 "<StartTime>00:00:00:00</StartTime>"
2893 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2894 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2895 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2904 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2905 auto subs_reel = make_shared<dcp::ReelSubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2907 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2909 check_verify_result (
2912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2914 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2920 /** Check that ResourceID and the MXF ID being the same is spotted */
2921 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2923 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2924 prepare_directory (dir);
2926 ASDCP::WriterInfo writer_info;
2927 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2930 auto mxf_id = dcp::make_uuid ();
2931 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2932 BOOST_REQUIRE (c == Kumu::UUID_Length);
2934 auto resource_id = mxf_id;
2935 ASDCP::TimedText::TimedTextDescriptor descriptor;
2936 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2937 DCP_ASSERT (c == Kumu::UUID_Length);
2939 auto xml_id = resource_id;
2940 ASDCP::TimedText::MXFWriter writer;
2941 auto subs_mxf = dir / "subs.mxf";
2942 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2943 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2944 writer.WriteTimedTextResource (dcp::String::compose(
2945 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2946 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2947 "<Id>urn:uuid:%1</Id>"
2948 "<ContentTitleText>Content</ContentTitleText>"
2949 "<AnnotationText>Annotation</AnnotationText>"
2950 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2951 "<ReelNumber>1</ReelNumber>"
2952 "<Language>en-US</Language>"
2953 "<EditRate>25 1</EditRate>"
2954 "<TimeCodeRate>25</TimeCodeRate>"
2955 "<StartTime>00:00:00:00</StartTime>"
2957 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2958 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2959 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2968 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2969 auto subs_reel = make_shared<dcp::ReelSubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2971 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2973 check_verify_result (
2976 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2977 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2978 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }