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_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_subtitle_asset.h"
51 #include "smpte_subtitle_asset.h"
52 #include "stereo_picture_asset.h"
53 #include "stream_operators.h"
57 #include "verify_j2k.h"
58 #include <boost/test/unit_test.hpp>
59 #include <boost/algorithm/string.hpp>
68 using std::make_shared;
69 using boost::optional;
70 using namespace boost::filesystem;
71 using std::shared_ptr;
74 static list<pair<string, optional<path>>> stages;
75 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
76 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
77 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
78 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
79 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
80 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
81 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
84 stage (string s, optional<path> p)
86 stages.push_back (make_pair (s, p));
96 prepare_directory (path path)
98 using namespace boost::filesystem;
100 create_directories (path);
105 setup (int reference_number, string verify_test_suffix)
107 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
108 prepare_directory (dir);
109 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
110 copy_file (i.path(), dir / i.path().filename());
119 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
121 auto reel = make_shared<dcp::Reel>();
122 reel->add (reel_asset);
123 reel->add (simple_markers());
125 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
127 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::ReelInteropSubtitleAsset>(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::ReelInteropSubtitleAsset>(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::ReelSMPTESubtitleAsset>(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::ReelSMPTESubtitleAsset>(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::ReelSMPTESubtitleAsset>(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, dcp::Standard::SMPTE);
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::String::compose("libdcp %1", dcp::version),
784 dcp::String::compose("libdcp %1", dcp::version),
785 dcp::LocalTime().as_string(),
791 path find_cpl (path dir)
793 for (auto i: directory_iterator(dir)) {
794 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
799 BOOST_REQUIRE (false);
804 /* DCP with invalid CompositionMetadataAsset */
805 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
807 using namespace boost::filesystem;
809 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
810 prepare_directory (dir);
812 auto reel = make_shared<dcp::Reel>();
813 reel->add (black_picture_asset(dir));
814 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
816 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
817 cpl->set_main_sound_sample_rate (48000);
818 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
819 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
820 cpl->set_version_number (1);
822 reel->add (simple_markers());
827 dcp::String::compose("libdcp %1", dcp::version),
828 dcp::String::compose("libdcp %1", dcp::version),
829 dcp::LocalTime().as_string(),
834 Editor e (find_cpl(dir));
835 e.replace ("MainSound", "MainSoundX");
838 check_verify_result (
841 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
842 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
844 dcp::VerificationNote::Type::ERROR,
845 dcp::VerificationNote::Code::INVALID_XML,
846 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
847 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
848 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
849 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
850 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
851 "ExtensionMetadataList?,)'"),
852 canonical(cpl->file().get()),
855 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
860 /* DCP with invalid CompositionMetadataAsset */
861 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
863 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
864 prepare_directory (dir);
866 auto reel = make_shared<dcp::Reel>();
867 reel->add (black_picture_asset(dir));
868 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
870 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
871 cpl->set_main_sound_sample_rate (48000);
872 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
873 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
878 dcp::String::compose("libdcp %1", dcp::version),
879 dcp::String::compose("libdcp %1", dcp::version),
880 dcp::LocalTime().as_string(),
885 Editor e (find_cpl(dir));
886 e.replace ("meta:Width", "meta:WidthX");
889 check_verify_result (
891 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
896 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
898 path const dir("build/test/verify_invalid_language1");
899 prepare_directory (dir);
900 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
901 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
902 asset->_language = "wrong-andbad";
903 asset->write (dir / "subs.mxf");
904 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
905 reel_asset->_language = "badlang";
906 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
908 check_verify_result (
911 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
918 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
919 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
921 path const dir("build/test/verify_invalid_language2");
922 prepare_directory (dir);
923 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
924 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
925 asset->_language = "wrong-andbad";
926 asset->write (dir / "subs.mxf");
927 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
928 reel_asset->_language = "badlang";
929 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
931 check_verify_result (
934 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
941 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
942 * the release territory.
944 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
946 path const dir("build/test/verify_invalid_language3");
947 prepare_directory (dir);
949 auto picture = simple_picture (dir, "foo");
950 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
951 auto reel = make_shared<dcp::Reel>();
952 reel->add (reel_picture);
953 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
954 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
955 reel->add (reel_sound);
956 reel->add (simple_markers());
958 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
960 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
961 cpl->_additional_subtitle_languages.push_back("andso-is-this");
962 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
963 cpl->set_main_sound_sample_rate (48000);
964 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
965 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
966 cpl->set_version_number (1);
967 cpl->_release_territory = "fred-jim";
968 auto dcp = make_shared<dcp::DCP>(dir);
971 dcp::String::compose("libdcp %1", dcp::version),
972 dcp::String::compose("libdcp %1", dcp::version),
973 dcp::LocalTime().as_string(),
977 check_verify_result (
980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
989 vector<dcp::VerificationNote>
990 check_picture_size (int width, int height, int frame_rate, bool three_d)
992 using namespace boost::filesystem;
994 path dcp_path = "build/test/verify_picture_test";
995 prepare_directory (dcp_path);
997 shared_ptr<dcp::PictureAsset> mp;
999 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1001 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1003 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1005 auto image = black_image (dcp::Size(width, height));
1006 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1007 int const length = three_d ? frame_rate * 2 : frame_rate;
1008 for (int i = 0; i < length; ++i) {
1009 picture_writer->write (j2c.data(), j2c.size());
1011 picture_writer->finalize ();
1013 auto d = make_shared<dcp::DCP>(dcp_path);
1014 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1015 cpl->set_annotation_text ("A Test DCP");
1016 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1017 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1018 cpl->set_main_sound_sample_rate (48000);
1019 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1020 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1021 cpl->set_version_number (1);
1023 auto reel = make_shared<dcp::Reel>();
1026 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1028 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1031 reel->add (simple_markers(frame_rate));
1037 dcp::String::compose("libdcp %1", dcp::version),
1038 dcp::String::compose("libdcp %1", dcp::version),
1039 dcp::LocalTime().as_string(),
1043 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1049 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1051 auto notes = check_picture_size(width, height, frame_rate, three_d);
1052 BOOST_CHECK_EQUAL (notes.size(), 0U);
1058 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1060 auto notes = check_picture_size(width, height, frame_rate, three_d);
1061 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1062 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1063 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1069 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1071 auto notes = check_picture_size(width, height, frame_rate, three_d);
1072 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1073 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1074 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1080 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1082 auto notes = check_picture_size(width, height, frame_rate, three_d);
1083 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1084 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1085 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1089 BOOST_AUTO_TEST_CASE (verify_picture_size)
1091 using namespace boost::filesystem;
1094 check_picture_size_ok (2048, 858, 24, false);
1095 check_picture_size_ok (2048, 858, 25, false);
1096 check_picture_size_ok (2048, 858, 48, false);
1097 check_picture_size_ok (2048, 858, 24, true);
1098 check_picture_size_ok (2048, 858, 25, true);
1099 check_picture_size_ok (2048, 858, 48, true);
1102 check_picture_size_ok (1998, 1080, 24, false);
1103 check_picture_size_ok (1998, 1080, 25, false);
1104 check_picture_size_ok (1998, 1080, 48, false);
1105 check_picture_size_ok (1998, 1080, 24, true);
1106 check_picture_size_ok (1998, 1080, 25, true);
1107 check_picture_size_ok (1998, 1080, 48, true);
1110 check_picture_size_ok (4096, 1716, 24, false);
1113 check_picture_size_ok (3996, 2160, 24, false);
1115 /* Bad frame size */
1116 check_picture_size_bad_frame_size (2050, 858, 24, false);
1117 check_picture_size_bad_frame_size (2048, 658, 25, false);
1118 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1119 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1121 /* Bad 2K frame rate */
1122 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1123 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1124 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1126 /* Bad 4K frame rate */
1127 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1128 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1131 auto notes = check_picture_size(3996, 2160, 24, true);
1132 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1133 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1134 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1140 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1143 make_shared<dcp::SubtitleString>(
1151 dcp::Time(start_frame, 24, 24),
1152 dcp::Time(end_frame, 24, 24),
1154 dcp::HAlign::CENTER,
1156 dcp::VAlign::CENTER,
1157 dcp::Direction::LTR,
1168 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1170 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1171 prepare_directory (dir);
1173 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1174 for (int i = 0; i < 2048; ++i) {
1175 add_test_subtitle (asset, i * 24, i * 24 + 20);
1177 asset->set_language (dcp::LanguageTag("de-DE"));
1178 asset->write (dir / "subs.mxf");
1179 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1180 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1182 check_verify_result (
1185 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1187 dcp::VerificationNote::Type::BV21_ERROR,
1188 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1190 canonical(dir / "subs.mxf")
1192 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1193 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1199 shared_ptr<dcp::SMPTESubtitleAsset>
1200 make_large_subtitle_asset (path font_file)
1202 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1203 dcp::ArrayData big_fake_font(1024 * 1024);
1204 big_fake_font.write (font_file);
1205 for (int i = 0; i < 116; ++i) {
1206 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1214 verify_timed_text_asset_too_large (string name)
1216 auto const dir = path("build/test") / name;
1217 prepare_directory (dir);
1218 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1219 add_test_subtitle (asset, 0, 240);
1220 asset->set_language (dcp::LanguageTag("de-DE"));
1221 asset->write (dir / "subs.mxf");
1223 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1224 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1226 check_verify_result (
1229 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1232 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1233 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1238 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1240 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1241 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1245 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1247 path dir = "build/test/verify_missing_subtitle_language";
1248 prepare_directory (dir);
1249 auto dcp = make_simple (dir, 1, 106);
1252 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1253 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1254 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1255 "<ContentTitleText>Content</ContentTitleText>"
1256 "<AnnotationText>Annotation</AnnotationText>"
1257 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1258 "<ReelNumber>1</ReelNumber>"
1259 "<EditRate>24 1</EditRate>"
1260 "<TimeCodeRate>24</TimeCodeRate>"
1261 "<StartTime>00:00:00:00</StartTime>"
1262 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1264 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1265 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1266 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1272 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1273 BOOST_REQUIRE (xml_file);
1274 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1276 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1277 subs->write (dir / "subs.mxf");
1279 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1280 dcp->cpls().front()->reels().front()->add(reel_subs);
1282 dcp::String::compose("libdcp %1", dcp::version),
1283 dcp::String::compose("libdcp %1", dcp::version),
1284 dcp::LocalTime().as_string(),
1288 check_verify_result (
1291 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1292 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1297 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1299 path path ("build/test/verify_mismatched_subtitle_languages");
1300 auto constexpr reel_length = 192;
1301 auto dcp = make_simple (path, 2, reel_length);
1302 auto cpl = dcp->cpls()[0];
1305 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1306 subs->set_language (dcp::LanguageTag("de-DE"));
1307 subs->add (simple_subtitle());
1308 subs->write (path / "subs1.mxf");
1309 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1310 cpl->reels()[0]->add(reel_subs);
1314 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1315 subs->set_language (dcp::LanguageTag("en-US"));
1316 subs->add (simple_subtitle());
1317 subs->write (path / "subs2.mxf");
1318 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1319 cpl->reels()[1]->add(reel_subs);
1323 dcp::String::compose("libdcp %1", dcp::version),
1324 dcp::String::compose("libdcp %1", dcp::version),
1325 dcp::LocalTime().as_string(),
1329 check_verify_result (
1332 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1339 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1341 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1342 auto constexpr reel_length = 192;
1343 auto dcp = make_simple (path, 2, reel_length);
1344 auto cpl = dcp->cpls()[0];
1347 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1348 ccaps->set_language (dcp::LanguageTag("de-DE"));
1349 ccaps->add (simple_subtitle());
1350 ccaps->write (path / "subs1.mxf");
1351 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1352 cpl->reels()[0]->add(reel_ccaps);
1356 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1357 ccaps->set_language (dcp::LanguageTag("en-US"));
1358 ccaps->add (simple_subtitle());
1359 ccaps->write (path / "subs2.mxf");
1360 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1361 cpl->reels()[1]->add(reel_ccaps);
1365 dcp::String::compose("libdcp %1", dcp::version),
1366 dcp::String::compose("libdcp %1", dcp::version),
1367 dcp::LocalTime().as_string(),
1371 check_verify_result (
1374 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1375 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1380 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1382 path dir = "build/test/verify_missing_subtitle_start_time";
1383 prepare_directory (dir);
1384 auto dcp = make_simple (dir, 1, 106);
1387 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1388 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1389 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1390 "<ContentTitleText>Content</ContentTitleText>"
1391 "<AnnotationText>Annotation</AnnotationText>"
1392 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1393 "<ReelNumber>1</ReelNumber>"
1394 "<Language>de-DE</Language>"
1395 "<EditRate>24 1</EditRate>"
1396 "<TimeCodeRate>24</TimeCodeRate>"
1397 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1399 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1400 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1401 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1407 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1408 BOOST_REQUIRE (xml_file);
1409 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1411 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1412 subs->write (dir / "subs.mxf");
1414 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1415 dcp->cpls().front()->reels().front()->add(reel_subs);
1417 dcp::String::compose("libdcp %1", dcp::version),
1418 dcp::String::compose("libdcp %1", dcp::version),
1419 dcp::LocalTime().as_string(),
1423 check_verify_result (
1426 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1427 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1432 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1434 path dir = "build/test/verify_invalid_subtitle_start_time";
1435 prepare_directory (dir);
1436 auto dcp = make_simple (dir, 1, 106);
1439 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1440 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1441 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1442 "<ContentTitleText>Content</ContentTitleText>"
1443 "<AnnotationText>Annotation</AnnotationText>"
1444 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1445 "<ReelNumber>1</ReelNumber>"
1446 "<Language>de-DE</Language>"
1447 "<EditRate>24 1</EditRate>"
1448 "<TimeCodeRate>24</TimeCodeRate>"
1449 "<StartTime>00:00:02:00</StartTime>"
1450 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1452 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1453 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1454 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1460 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1461 BOOST_REQUIRE (xml_file);
1462 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1464 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1465 subs->write (dir / "subs.mxf");
1467 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1468 dcp->cpls().front()->reels().front()->add(reel_subs);
1470 dcp::String::compose("libdcp %1", dcp::version),
1471 dcp::String::compose("libdcp %1", dcp::version),
1472 dcp::LocalTime().as_string(),
1476 check_verify_result (
1479 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1480 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1488 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1491 , v_position(v_position_)
1503 shared_ptr<dcp::CPL>
1504 dcp_with_text (path dir, vector<TestText> subs)
1506 prepare_directory (dir);
1507 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1508 asset->set_start_time (dcp::Time());
1509 for (auto i: subs) {
1510 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1512 asset->set_language (dcp::LanguageTag("de-DE"));
1513 asset->write (dir / "subs.mxf");
1515 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1516 return write_dcp_with_single_asset (dir, reel_asset);
1520 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1522 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1523 /* Just too early */
1524 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1525 check_verify_result (
1528 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1529 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1535 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1537 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1538 /* Just late enough */
1539 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1540 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1544 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1546 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1547 prepare_directory (dir);
1549 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1550 asset1->set_start_time (dcp::Time());
1551 /* Just late enough */
1552 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1553 asset1->set_language (dcp::LanguageTag("de-DE"));
1554 asset1->write (dir / "subs1.mxf");
1555 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1556 auto reel1 = make_shared<dcp::Reel>();
1557 reel1->add (reel_asset1);
1558 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1559 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1560 reel1->add (markers1);
1562 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1563 asset2->set_start_time (dcp::Time());
1564 /* This would be too early on first reel but should be OK on the second */
1565 add_test_subtitle (asset2, 3, 4 * 24);
1566 asset2->set_language (dcp::LanguageTag("de-DE"));
1567 asset2->write (dir / "subs2.mxf");
1568 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1569 auto reel2 = make_shared<dcp::Reel>();
1570 reel2->add (reel_asset2);
1571 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1572 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1573 reel2->add (markers2);
1575 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1578 auto dcp = make_shared<dcp::DCP>(dir);
1581 dcp::String::compose("libdcp %1", dcp::version),
1582 dcp::String::compose("libdcp %1", dcp::version),
1583 dcp::LocalTime().as_string(),
1588 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1592 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1594 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1595 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1599 { 5 * 24 + 1, 6 * 24 },
1601 check_verify_result (
1604 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1605 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1610 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1612 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1613 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1617 { 5 * 24 + 16, 8 * 24 },
1619 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1623 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1625 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1626 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1627 check_verify_result (
1630 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1631 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1636 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1638 auto const dir = path("build/test/verify_valid_subtitle_duration");
1639 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1640 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1644 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1646 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1647 prepare_directory (dir);
1648 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1649 asset->set_start_time (dcp::Time());
1650 add_test_subtitle (asset, 0, 4 * 24);
1651 asset->set_language (dcp::LanguageTag("de-DE"));
1652 asset->write (dir / "subs.mxf");
1654 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1655 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1656 check_verify_result (
1659 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1660 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1661 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1668 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1670 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1671 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1674 { 96, 200, 0.0, "We" },
1675 { 96, 200, 0.1, "have" },
1676 { 96, 200, 0.2, "four" },
1677 { 96, 200, 0.3, "lines" }
1679 check_verify_result (
1682 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1683 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1688 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1690 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1691 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1694 { 96, 200, 0.0, "We" },
1695 { 96, 200, 0.1, "have" },
1696 { 96, 200, 0.2, "four" },
1698 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1702 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1704 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1705 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1708 { 96, 300, 0.0, "We" },
1709 { 96, 300, 0.1, "have" },
1710 { 150, 180, 0.2, "four" },
1711 { 150, 180, 0.3, "lines" }
1713 check_verify_result (
1716 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1722 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1724 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1725 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1728 { 96, 300, 0.0, "We" },
1729 { 96, 300, 0.1, "have" },
1730 { 150, 180, 0.2, "four" },
1731 { 190, 250, 0.3, "lines" }
1733 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1737 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1739 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1740 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1743 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1745 check_verify_result (
1748 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1749 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1754 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1756 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1757 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1760 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1762 check_verify_result (
1765 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1766 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1771 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1773 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1774 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1777 { 96, 200, 0.0, "We" },
1778 { 96, 200, 0.1, "have" },
1779 { 96, 200, 0.2, "four" },
1780 { 96, 200, 0.3, "lines" }
1782 check_verify_result (
1785 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1791 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1793 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1794 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1797 { 96, 200, 0.0, "We" },
1798 { 96, 200, 0.1, "have" },
1799 { 96, 200, 0.2, "four" },
1801 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1805 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1807 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1808 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1811 { 96, 300, 0.0, "We" },
1812 { 96, 300, 0.1, "have" },
1813 { 150, 180, 0.2, "four" },
1814 { 150, 180, 0.3, "lines" }
1816 check_verify_result (
1819 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1820 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1825 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1827 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1828 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1831 { 96, 300, 0.0, "We" },
1832 { 96, 300, 0.1, "have" },
1833 { 150, 180, 0.2, "four" },
1834 { 190, 250, 0.3, "lines" }
1836 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1840 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1842 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1843 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1846 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1848 check_verify_result (
1851 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1852 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1857 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1859 path const dir("build/test/verify_invalid_sound_frame_rate");
1860 prepare_directory (dir);
1862 auto picture = simple_picture (dir, "foo");
1863 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1864 auto reel = make_shared<dcp::Reel>();
1865 reel->add (reel_picture);
1866 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1867 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1868 reel->add (reel_sound);
1869 reel->add (simple_markers());
1870 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1872 auto dcp = make_shared<dcp::DCP>(dir);
1875 dcp::String::compose("libdcp %1", dcp::version),
1876 dcp::String::compose("libdcp %1", dcp::version),
1877 dcp::LocalTime().as_string(),
1881 check_verify_result (
1884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1890 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1892 path const dir("build/test/verify_missing_cpl_annotation_text");
1893 auto dcp = make_simple (dir);
1895 dcp::String::compose("libdcp %1", dcp::version),
1896 dcp::String::compose("libdcp %1", dcp::version),
1897 dcp::LocalTime().as_string(),
1901 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1903 auto const cpl = dcp->cpls()[0];
1906 BOOST_REQUIRE (cpl->file());
1907 Editor e(cpl->file().get());
1908 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1911 check_verify_result (
1914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1915 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1920 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1922 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1923 auto dcp = make_simple (dir);
1925 dcp::String::compose("libdcp %1", dcp::version),
1926 dcp::String::compose("libdcp %1", dcp::version),
1927 dcp::LocalTime().as_string(),
1931 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1932 auto const cpl = dcp->cpls()[0];
1935 BOOST_REQUIRE (cpl->file());
1936 Editor e(cpl->file().get());
1937 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1940 check_verify_result (
1943 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1944 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1949 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1951 path const dir("build/test/verify_mismatched_asset_duration");
1952 prepare_directory (dir);
1953 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1954 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1956 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1957 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1959 auto reel = make_shared<dcp::Reel>(
1960 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1961 make_shared<dcp::ReelSoundAsset>(ms, 0)
1964 reel->add (simple_markers());
1969 dcp::String::compose("libdcp %1", dcp::version),
1970 dcp::String::compose("libdcp %1", dcp::version),
1971 dcp::LocalTime().as_string(),
1975 check_verify_result (
1978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1986 shared_ptr<dcp::CPL>
1987 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1989 prepare_directory (dir);
1990 auto dcp = make_shared<dcp::DCP>(dir);
1991 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1993 auto constexpr reel_length = 192;
1995 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1996 subs->set_language (dcp::LanguageTag("de-DE"));
1997 subs->set_start_time (dcp::Time());
1998 subs->add (simple_subtitle());
1999 subs->write (dir / "subs.mxf");
2000 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2002 auto reel1 = make_shared<dcp::Reel>(
2003 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2004 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2008 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2011 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2012 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2013 reel1->add (markers1);
2017 auto reel2 = 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 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2026 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2027 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2028 reel2->add (markers2);
2034 dcp::String::compose("libdcp %1", dcp::version),
2035 dcp::String::compose("libdcp %1", dcp::version),
2036 dcp::LocalTime().as_string(),
2044 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2047 path dir ("build/test/missing_main_subtitle_from_some_reels");
2048 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2049 check_verify_result (
2052 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2053 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2059 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2060 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2061 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2065 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2066 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2067 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2073 shared_ptr<dcp::CPL>
2074 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2076 prepare_directory (dir);
2077 auto dcp = make_shared<dcp::DCP>(dir);
2078 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2080 auto constexpr reel_length = 192;
2082 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2083 subs->set_language (dcp::LanguageTag("de-DE"));
2084 subs->set_start_time (dcp::Time());
2085 subs->add (simple_subtitle());
2086 subs->write (dir / "subs.mxf");
2088 auto reel1 = make_shared<dcp::Reel>(
2089 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2090 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2093 for (int i = 0; i < caps_in_reel1; ++i) {
2094 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2097 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2098 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2099 reel1->add (markers1);
2103 auto reel2 = make_shared<dcp::Reel>(
2104 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2105 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2108 for (int i = 0; i < caps_in_reel2; ++i) {
2109 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2112 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2113 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2114 reel2->add (markers2);
2120 dcp::String::compose("libdcp %1", dcp::version),
2121 dcp::String::compose("libdcp %1", dcp::version),
2122 dcp::LocalTime().as_string(),
2130 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2133 path dir ("build/test/mismatched_closed_caption_asset_counts");
2134 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2135 check_verify_result (
2138 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2139 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2144 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2145 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2146 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2150 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2151 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2152 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2159 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2161 prepare_directory (dir);
2162 auto dcp = make_shared<dcp::DCP>(dir);
2163 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2165 auto constexpr reel_length = 192;
2167 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2168 subs->set_language (dcp::LanguageTag("de-DE"));
2169 subs->set_start_time (dcp::Time());
2170 subs->add (simple_subtitle());
2171 subs->write (dir / "subs.mxf");
2172 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2175 auto reel = make_shared<dcp::Reel>(
2176 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2177 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2180 reel->add (reel_text);
2182 reel->add (simple_markers(reel_length));
2188 dcp::String::compose("libdcp %1", dcp::version),
2189 dcp::String::compose("libdcp %1", dcp::version),
2190 dcp::LocalTime().as_string(),
2194 check_verify_result (
2197 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2198 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2203 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2205 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2206 "build/test/verify_subtitle_entry_point_must_be_present",
2207 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2208 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2209 asset->unset_entry_point ();
2213 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2214 "build/test/verify_subtitle_entry_point_must_be_zero",
2215 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2216 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2217 asset->set_entry_point (4);
2221 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2222 "build/test/verify_closed_caption_entry_point_must_be_present",
2223 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2224 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2225 asset->unset_entry_point ();
2229 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2230 "build/test/verify_closed_caption_entry_point_must_be_zero",
2231 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2232 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2233 asset->set_entry_point (9);
2239 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2243 path const dir("build/test/verify_missing_hash");
2244 auto dcp = make_simple (dir);
2246 dcp::String::compose("libdcp %1", dcp::version),
2247 dcp::String::compose("libdcp %1", dcp::version),
2248 dcp::LocalTime().as_string(),
2252 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2253 auto const cpl = dcp->cpls()[0];
2256 BOOST_REQUIRE (cpl->file());
2257 Editor e(cpl->file().get());
2258 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2261 check_verify_result (
2264 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2265 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2272 verify_markers_test (
2274 vector<pair<dcp::Marker, dcp::Time>> markers,
2275 vector<dcp::VerificationNote> test_notes
2278 auto dcp = make_simple (dir);
2279 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2280 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2281 for (auto const& i: markers) {
2282 markers_asset->set (i.first, i.second);
2284 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2286 dcp::String::compose("libdcp %1", dcp::version),
2287 dcp::String::compose("libdcp %1", dcp::version),
2288 dcp::LocalTime().as_string(),
2292 check_verify_result ({dir}, test_notes);
2296 BOOST_AUTO_TEST_CASE (verify_markers)
2298 verify_markers_test (
2299 "build/test/verify_markers_all_correct",
2301 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2302 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2303 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2304 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2309 verify_markers_test (
2310 "build/test/verify_markers_missing_ffec",
2312 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2313 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2314 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2317 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2320 verify_markers_test (
2321 "build/test/verify_markers_missing_ffmc",
2323 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2324 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2325 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2328 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2331 verify_markers_test (
2332 "build/test/verify_markers_missing_ffoc",
2334 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2335 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2336 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2339 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2342 verify_markers_test (
2343 "build/test/verify_markers_missing_lfoc",
2345 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2346 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2347 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2350 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2353 verify_markers_test (
2354 "build/test/verify_markers_incorrect_ffoc",
2356 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2357 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2358 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2359 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2362 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2365 verify_markers_test (
2366 "build/test/verify_markers_incorrect_lfoc",
2368 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2369 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2370 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2371 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2374 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2379 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2381 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2382 prepare_directory (dir);
2383 auto dcp = make_simple (dir);
2384 auto cpl = dcp->cpls()[0];
2385 cpl->unset_version_number();
2387 dcp::String::compose("libdcp %1", dcp::version),
2388 dcp::String::compose("libdcp %1", dcp::version),
2389 dcp::LocalTime().as_string(),
2393 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2397 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2399 path dir = "build/test/verify_missing_extension_metadata1";
2400 auto dcp = make_simple (dir);
2402 dcp::String::compose("libdcp %1", dcp::version),
2403 dcp::String::compose("libdcp %1", dcp::version),
2404 dcp::LocalTime().as_string(),
2408 auto cpl = dcp->cpls()[0];
2411 Editor e (cpl->file().get());
2412 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2415 check_verify_result (
2418 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2419 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2424 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2426 path dir = "build/test/verify_missing_extension_metadata2";
2427 auto dcp = make_simple (dir);
2429 dcp::String::compose("libdcp %1", dcp::version),
2430 dcp::String::compose("libdcp %1", dcp::version),
2431 dcp::LocalTime().as_string(),
2435 auto cpl = dcp->cpls()[0];
2438 Editor e (cpl->file().get());
2439 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2442 check_verify_result (
2445 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2446 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2451 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2453 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2454 auto dcp = make_simple (dir);
2456 dcp::String::compose("libdcp %1", dcp::version),
2457 dcp::String::compose("libdcp %1", dcp::version),
2458 dcp::LocalTime().as_string(),
2462 auto const cpl = dcp->cpls()[0];
2465 Editor e (cpl->file().get());
2466 e.replace ("<meta:Name>A", "<meta:NameX>A");
2467 e.replace ("n</meta:Name>", "n</meta:NameX>");
2470 check_verify_result (
2473 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2474 { 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 },
2475 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2480 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2482 path dir = "build/test/verify_invalid_extension_metadata1";
2483 auto dcp = make_simple (dir);
2485 dcp::String::compose("libdcp %1", dcp::version),
2486 dcp::String::compose("libdcp %1", dcp::version),
2487 dcp::LocalTime().as_string(),
2491 auto cpl = dcp->cpls()[0];
2494 Editor e (cpl->file().get());
2495 e.replace ("Application", "Fred");
2498 check_verify_result (
2501 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2502 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2507 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2509 path dir = "build/test/verify_invalid_extension_metadata2";
2510 auto dcp = make_simple (dir);
2512 dcp::String::compose("libdcp %1", dcp::version),
2513 dcp::String::compose("libdcp %1", dcp::version),
2514 dcp::LocalTime().as_string(),
2518 auto cpl = dcp->cpls()[0];
2521 Editor e (cpl->file().get());
2522 e.replace ("DCP Constraints Profile", "Fred");
2525 check_verify_result (
2528 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2529 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2534 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2536 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2537 auto dcp = make_simple (dir);
2539 dcp::String::compose("libdcp %1", dcp::version),
2540 dcp::String::compose("libdcp %1", dcp::version),
2541 dcp::LocalTime().as_string(),
2545 auto const cpl = dcp->cpls()[0];
2548 Editor e (cpl->file().get());
2549 e.replace ("<meta:Value>", "<meta:ValueX>");
2550 e.replace ("</meta:Value>", "</meta:ValueX>");
2553 check_verify_result (
2556 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2557 { 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 },
2558 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2563 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2565 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2566 auto dcp = make_simple (dir);
2568 dcp::String::compose("libdcp %1", dcp::version),
2569 dcp::String::compose("libdcp %1", dcp::version),
2570 dcp::LocalTime().as_string(),
2574 auto const cpl = dcp->cpls()[0];
2577 Editor e (cpl->file().get());
2578 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2581 check_verify_result (
2584 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2585 { 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() },
2590 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2592 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2593 auto dcp = make_simple (dir);
2595 dcp::String::compose("libdcp %1", dcp::version),
2596 dcp::String::compose("libdcp %1", dcp::version),
2597 dcp::LocalTime().as_string(),
2601 auto const cpl = dcp->cpls()[0];
2604 Editor e (cpl->file().get());
2605 e.replace ("<meta:Property>", "<meta:PropertyX>");
2606 e.replace ("</meta:Property>", "</meta:PropertyX>");
2609 check_verify_result (
2612 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2613 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2614 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2619 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2621 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2622 auto dcp = make_simple (dir);
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:PropertyList>", "<meta:PropertyListX>");
2635 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2638 check_verify_result (
2641 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2642 { 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 },
2643 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2649 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2651 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2652 prepare_directory (dir);
2653 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2654 copy_file (i.path(), dir / i.path().filename());
2657 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2658 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2662 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2665 check_verify_result (
2668 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2669 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2670 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2672 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2673 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2680 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2682 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2683 prepare_directory (dir);
2684 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2685 copy_file (i.path(), dir / i.path().filename());
2688 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2689 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2692 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2695 check_verify_result (
2698 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2699 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2701 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2702 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2704 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2709 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2711 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2712 prepare_directory (dir);
2713 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2714 copy_file (i.path(), dir / i.path().filename());
2718 Editor e (dir / dcp_test1_pkl);
2719 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2722 check_verify_result ({dir}, {});
2726 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2728 path dir ("build/test/verify_must_not_be_partially_encrypted");
2729 prepare_directory (dir);
2733 auto signer = make_shared<dcp::CertificateChain>();
2734 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2735 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2736 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2737 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2739 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2743 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2746 auto writer = mp->start_write (dir / "video.mxf", false);
2747 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2748 for (int i = 0; i < 24; ++i) {
2749 writer->write (j2c.data(), j2c.size());
2751 writer->finalize ();
2753 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2755 auto reel = make_shared<dcp::Reel>(
2756 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2757 make_shared<dcp::ReelSoundAsset>(ms, 0)
2760 reel->add (simple_markers());
2764 cpl->set_content_version (
2765 {"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"}
2767 cpl->set_annotation_text ("A Test DCP");
2768 cpl->set_issuer ("OpenDCP 0.0.25");
2769 cpl->set_creator ("OpenDCP 0.0.25");
2770 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2771 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2772 cpl->set_main_sound_sample_rate (48000);
2773 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2774 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2775 cpl->set_version_number (1);
2779 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2781 check_verify_result (
2784 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2789 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2791 vector<dcp::VerificationNote> notes;
2792 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"));
2793 auto reader = picture.start_read ();
2794 auto frame = reader->get_frame (0);
2795 verify_j2k (frame, notes);
2796 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2800 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2802 vector<dcp::VerificationNote> notes;
2803 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2804 auto reader = picture.start_read ();
2805 auto frame = reader->get_frame (0);
2806 verify_j2k (frame, notes);
2807 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2811 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2813 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2814 prepare_directory (dir);
2815 auto dcp = make_simple (dir);
2817 vector<dcp::VerificationNote> notes;
2818 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2819 auto reader = picture.start_read ();
2820 auto frame = reader->get_frame (0);
2821 verify_j2k (frame, notes);
2822 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2826 /** Check that ResourceID and the XML ID being different is spotted */
2827 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2829 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2830 prepare_directory (dir);
2832 ASDCP::WriterInfo writer_info;
2833 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2836 auto mxf_id = dcp::make_uuid ();
2837 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2838 BOOST_REQUIRE (c == Kumu::UUID_Length);
2840 auto resource_id = dcp::make_uuid ();
2841 ASDCP::TimedText::TimedTextDescriptor descriptor;
2842 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2843 DCP_ASSERT (c == Kumu::UUID_Length);
2845 auto xml_id = dcp::make_uuid ();
2846 ASDCP::TimedText::MXFWriter writer;
2847 auto subs_mxf = dir / "subs.mxf";
2848 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2849 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2850 writer.WriteTimedTextResource (dcp::String::compose(
2851 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2852 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2853 "<Id>urn:uuid:%1</Id>"
2854 "<ContentTitleText>Content</ContentTitleText>"
2855 "<AnnotationText>Annotation</AnnotationText>"
2856 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2857 "<ReelNumber>1</ReelNumber>"
2858 "<Language>en-US</Language>"
2859 "<EditRate>25 1</EditRate>"
2860 "<TimeCodeRate>25</TimeCodeRate>"
2861 "<StartTime>00:00:00:00</StartTime>"
2863 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2864 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2865 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2874 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2875 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2877 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2879 check_verify_result (
2882 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2884 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2890 /** Check that ResourceID and the MXF ID being the same is spotted */
2891 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2893 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2894 prepare_directory (dir);
2896 ASDCP::WriterInfo writer_info;
2897 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2900 auto mxf_id = dcp::make_uuid ();
2901 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2902 BOOST_REQUIRE (c == Kumu::UUID_Length);
2904 auto resource_id = mxf_id;
2905 ASDCP::TimedText::TimedTextDescriptor descriptor;
2906 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2907 DCP_ASSERT (c == Kumu::UUID_Length);
2909 auto xml_id = resource_id;
2910 ASDCP::TimedText::MXFWriter writer;
2911 auto subs_mxf = dir / "subs.mxf";
2912 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2913 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2914 writer.WriteTimedTextResource (dcp::String::compose(
2915 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2916 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2917 "<Id>urn:uuid:%1</Id>"
2918 "<ContentTitleText>Content</ContentTitleText>"
2919 "<AnnotationText>Annotation</AnnotationText>"
2920 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2921 "<ReelNumber>1</ReelNumber>"
2922 "<Language>en-US</Language>"
2923 "<EditRate>25 1</EditRate>"
2924 "<TimeCodeRate>25</TimeCodeRate>"
2925 "<StartTime>00:00:00:00</StartTime>"
2927 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2928 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2929 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2938 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2939 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2941 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2943 check_verify_result (
2946 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2948 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2949 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }