2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_interop_closed_caption_asset.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_closed_caption_asset.h"
51 #include "reel_smpte_subtitle_asset.h"
52 #include "smpte_subtitle_asset.h"
53 #include "stereo_picture_asset.h"
54 #include "stream_operators.h"
58 #include "verify_j2k.h"
59 #include <boost/test/unit_test.hpp>
60 #include <boost/algorithm/string.hpp>
70 using std::make_shared;
71 using boost::optional;
72 using namespace boost::filesystem;
73 using std::shared_ptr;
76 static list<pair<string, optional<path>>> stages;
77 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
78 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
79 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
80 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
81 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
82 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
83 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
86 stage (string s, optional<path> p)
88 stages.push_back (make_pair (s, p));
98 prepare_directory (path path)
100 using namespace boost::filesystem;
102 create_directories (path);
107 setup (int reference_number, string verify_test_suffix)
109 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
110 prepare_directory (dir);
111 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
112 copy_file (i.path(), dir / i.path().filename());
121 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
123 auto reel = make_shared<dcp::Reel>();
124 reel->add (reel_asset);
125 reel->add (simple_markers());
127 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
129 auto dcp = make_shared<dcp::DCP>(dir);
132 dcp::String::compose("libdcp %1", dcp::version),
133 dcp::String::compose("libdcp %1", dcp::version),
134 dcp::LocalTime().as_string(),
142 /** Class that can alter a file by searching and replacing strings within it.
143 * On destruction modifies the file whose name was given to the constructor.
151 _content = dcp::file_to_string (_path);
156 auto f = fopen(_path.string().c_str(), "w");
158 fwrite (_content.c_str(), _content.length(), 1, f);
162 void replace (string a, string b)
164 auto old_content = _content;
165 boost::algorithm::replace_all (_content, a, b);
166 BOOST_REQUIRE (_content != old_content);
169 void delete_lines (string from, string to)
171 vector<string> lines;
172 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
173 bool deleting = false;
174 auto old_content = _content;
176 for (auto i: lines) {
177 if (i.find(from) != string::npos) {
181 _content += i + "\n";
183 if (deleting && i.find(to) != string::npos) {
187 BOOST_REQUIRE (_content != old_content);
192 std::string _content;
196 LIBDCP_DISABLE_WARNINGS
199 dump_notes (vector<dcp::VerificationNote> const & notes)
201 for (auto i: notes) {
202 std::cout << dcp::note_to_string(i) << "\n";
205 LIBDCP_ENABLE_WARNINGS
210 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
212 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
213 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
214 std::sort (notes.begin(), notes.end());
215 std::sort (test_notes.begin(), test_notes.end());
216 for (auto i = 0U; i < notes.size(); ++i) {
217 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
224 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
226 auto dir = setup (1, suffix);
229 Editor e (file(suffix));
230 e.replace (from, to);
233 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
235 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
236 auto i = notes.begin();
237 auto j = codes.begin();
238 while (i != notes.end()) {
239 BOOST_CHECK_EQUAL (i->code(), *j);
246 BOOST_AUTO_TEST_CASE (verify_no_error)
249 auto dir = setup (1, "no_error");
250 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
252 path const cpl_file = dir / dcp_test1_cpl;
253 path const pkl_file = dir / dcp_test1_pkl;
254 path const assetmap_file = dir / "ASSETMAP.xml";
256 auto st = stages.begin();
257 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
258 BOOST_REQUIRE (st->second);
259 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
261 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
262 BOOST_REQUIRE (st->second);
263 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
265 BOOST_CHECK_EQUAL (st->first, "Checking reel");
266 BOOST_REQUIRE (!st->second);
268 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
269 BOOST_REQUIRE (st->second);
270 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
272 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
273 BOOST_REQUIRE (st->second);
274 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
276 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
277 BOOST_REQUIRE (st->second);
278 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
280 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
281 BOOST_REQUIRE (st->second);
282 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
284 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
285 BOOST_REQUIRE (st->second);
286 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
288 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
289 BOOST_REQUIRE (st->second);
290 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
292 BOOST_REQUIRE (st == stages.end());
294 BOOST_CHECK_EQUAL (notes.size(), 0);
298 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
300 using namespace boost::filesystem;
302 auto dir = setup (1, "incorrect_picture_sound_hash");
304 auto video_path = path(dir / "video.mxf");
305 auto mod = fopen(video_path.string().c_str(), "r+b");
307 fseek (mod, 4096, SEEK_SET);
309 fwrite (&x, sizeof(x), 1, mod);
312 auto audio_path = path(dir / "audio.mxf");
313 mod = fopen(audio_path.string().c_str(), "r+b");
315 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
316 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
319 dcp::ASDCPErrorSuspender sus;
320 check_verify_result (
323 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
324 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
329 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
331 using namespace boost::filesystem;
333 auto dir = setup (1, "mismatched_picture_sound_hashes");
336 Editor e (dir / dcp_test1_pkl);
337 e.replace ("<Hash>", "<Hash>x");
340 check_verify_result (
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
345 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
346 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
347 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
348 { 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 }
353 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
355 auto dir = setup (1, "failed_read_content_kind");
358 Editor e (dir / dcp_test1_cpl);
359 e.replace ("<ContentKind>", "<ContentKind>x");
362 check_verify_result (
364 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
373 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
381 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
387 asset_map (string suffix)
389 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
393 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
395 check_verify_result_after_replace (
396 "invalid_picture_frame_rate", &cpl,
397 "<FrameRate>24 1", "<FrameRate>99 1",
398 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
399 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
403 BOOST_AUTO_TEST_CASE (verify_missing_asset)
405 auto dir = setup (1, "missing_asset");
406 remove (dir / "video.mxf");
407 check_verify_result (
410 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
415 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
417 check_verify_result_after_replace (
418 "empty_asset_path", &asset_map,
419 "<Path>video.mxf</Path>", "<Path></Path>",
420 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
425 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
427 check_verify_result_after_replace (
428 "mismatched_standard", &cpl,
429 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
430 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::INVALID_XML,
433 dcp::VerificationNote::Code::INVALID_XML,
434 dcp::VerificationNote::Code::INVALID_XML,
435 dcp::VerificationNote::Code::INVALID_XML,
436 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
441 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
443 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
444 check_verify_result_after_replace (
445 "invalid_xml_cpl_id", &cpl,
446 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
447 { dcp::VerificationNote::Code::INVALID_XML }
452 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
454 check_verify_result_after_replace (
455 "invalid_xml_issue_date", &cpl,
456 "<IssueDate>", "<IssueDate>x",
457 { dcp::VerificationNote::Code::INVALID_XML,
458 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
463 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
465 check_verify_result_after_replace (
466 "invalid_xml_pkl_id", &pkl,
467 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
468 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
469 { dcp::VerificationNote::Code::INVALID_XML }
474 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
476 check_verify_result_after_replace (
477 "invalix_xml_asset_map_id", &asset_map,
478 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
479 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
480 { dcp::VerificationNote::Code::INVALID_XML }
485 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
488 auto dir = setup (3, "verify_invalid_standard");
489 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
491 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
492 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
493 path const assetmap_file = dir / "ASSETMAP";
495 auto st = stages.begin();
496 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
497 BOOST_REQUIRE (st->second);
498 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
500 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
501 BOOST_REQUIRE (st->second);
502 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
504 BOOST_CHECK_EQUAL (st->first, "Checking reel");
505 BOOST_REQUIRE (!st->second);
507 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
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 picture frame sizes");
512 BOOST_REQUIRE (st->second);
513 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
515 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
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 sound asset metadata");
520 BOOST_REQUIRE (st->second);
521 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
523 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
524 BOOST_REQUIRE (st->second);
525 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
527 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
528 BOOST_REQUIRE (st->second);
529 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
531 BOOST_REQUIRE (st == stages.end());
533 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
534 auto i = notes.begin ();
535 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
536 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
538 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
539 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
542 /* DCP with a short asset */
543 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
545 auto dir = setup (8, "invalid_duration");
546 check_verify_result (
549 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
550 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
551 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
552 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
553 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
554 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
561 dcp_from_frame (dcp::ArrayData const& frame, path dir)
563 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
564 create_directories (dir);
565 auto writer = asset->start_write (dir / "pic.mxf", true);
566 for (int i = 0; i < 24; ++i) {
567 writer->write (frame.data(), frame.size());
571 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
572 return write_dcp_with_single_asset (dir, reel_asset);
576 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
578 int const too_big = 1302083 * 2;
580 /* Compress a black image */
581 auto image = black_image ();
582 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
583 BOOST_REQUIRE (frame.size() < too_big);
585 /* Place it in a bigger block with some zero padding at the end */
586 dcp::ArrayData oversized_frame(too_big);
587 memcpy (oversized_frame.data(), frame.data(), frame.size());
588 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
590 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
591 prepare_directory (dir);
592 auto cpl = dcp_from_frame (oversized_frame, dir);
594 check_verify_result (
597 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
598 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
599 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
604 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
606 int const nearly_too_big = 1302083 * 0.98;
608 /* Compress a black image */
609 auto image = black_image ();
610 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
611 BOOST_REQUIRE (frame.size() < nearly_too_big);
613 /* Place it in a bigger block with some zero padding at the end */
614 dcp::ArrayData oversized_frame(nearly_too_big);
615 memcpy (oversized_frame.data(), frame.data(), frame.size());
616 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
618 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
619 prepare_directory (dir);
620 auto cpl = dcp_from_frame (oversized_frame, dir);
622 check_verify_result (
625 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
626 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
627 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
632 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
634 /* Compress a black image */
635 auto image = black_image ();
636 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
637 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
639 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
640 prepare_directory (dir);
641 auto cpl = dcp_from_frame (frame, dir);
643 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
647 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
649 path const dir("build/test/verify_valid_interop_subtitles");
650 prepare_directory (dir);
651 copy_file ("test/data/subs1.xml", dir / "subs.xml");
652 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
653 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
654 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
656 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
660 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
662 using namespace boost::filesystem;
664 path const dir("build/test/verify_invalid_interop_subtitles");
665 prepare_directory (dir);
666 copy_file ("test/data/subs1.xml", dir / "subs.xml");
667 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
668 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
669 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
672 Editor e (dir / "subs.xml");
673 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
676 check_verify_result (
679 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
680 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
682 dcp::VerificationNote::Type::ERROR,
683 dcp::VerificationNote::Code::INVALID_XML,
684 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
692 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
694 path const dir("build/test/verify_valid_smpte_subtitles");
695 prepare_directory (dir);
696 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
697 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
698 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
699 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
701 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
705 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
707 using namespace boost::filesystem;
709 path const dir("build/test/verify_invalid_smpte_subtitles");
710 prepare_directory (dir);
711 /* This broken_smpte.mxf does not use urn:uuid: for its subtitle ID, which we tolerate (rightly or wrongly) */
712 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
713 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
714 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
715 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
717 check_verify_result (
720 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
722 dcp::VerificationNote::Type::ERROR,
723 dcp::VerificationNote::Code::INVALID_XML,
724 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
728 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
734 BOOST_AUTO_TEST_CASE (verify_external_asset)
736 path const ov_dir("build/test/verify_external_asset");
737 prepare_directory (ov_dir);
739 auto image = black_image ();
740 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
741 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
742 dcp_from_frame (frame, ov_dir);
744 dcp::DCP ov (ov_dir);
747 path const vf_dir("build/test/verify_external_asset_vf");
748 prepare_directory (vf_dir);
750 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
751 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
753 check_verify_result (
756 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
762 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
764 path const dir("build/test/verify_valid_cpl_metadata");
765 prepare_directory (dir);
767 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
768 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
769 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
771 auto reel = make_shared<dcp::Reel>();
772 reel->add (reel_asset);
774 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
775 reel->add (simple_markers(16 * 24));
777 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
779 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
780 cpl->set_main_sound_sample_rate (48000);
781 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
782 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
783 cpl->set_version_number (1);
788 dcp::String::compose("libdcp %1", dcp::version),
789 dcp::String::compose("libdcp %1", dcp::version),
790 dcp::LocalTime().as_string(),
796 path find_cpl (path dir)
798 for (auto i: directory_iterator(dir)) {
799 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
804 BOOST_REQUIRE (false);
809 /* DCP with invalid CompositionMetadataAsset */
810 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
812 using namespace boost::filesystem;
814 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
815 prepare_directory (dir);
817 auto reel = make_shared<dcp::Reel>();
818 reel->add (black_picture_asset(dir));
819 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
821 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
822 cpl->set_main_sound_sample_rate (48000);
823 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
824 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
825 cpl->set_version_number (1);
827 reel->add (simple_markers());
832 dcp::String::compose("libdcp %1", dcp::version),
833 dcp::String::compose("libdcp %1", dcp::version),
834 dcp::LocalTime().as_string(),
839 Editor e (find_cpl(dir));
840 e.replace ("MainSound", "MainSoundX");
843 check_verify_result (
846 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
847 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
849 dcp::VerificationNote::Type::ERROR,
850 dcp::VerificationNote::Code::INVALID_XML,
851 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
852 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
853 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
854 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
855 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
856 "ExtensionMetadataList?,)'"),
857 canonical(cpl->file().get()),
860 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
865 /* DCP with invalid CompositionMetadataAsset */
866 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
868 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
869 prepare_directory (dir);
871 auto reel = make_shared<dcp::Reel>();
872 reel->add (black_picture_asset(dir));
873 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
875 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
876 cpl->set_main_sound_sample_rate (48000);
877 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
878 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
883 dcp::String::compose("libdcp %1", dcp::version),
884 dcp::String::compose("libdcp %1", dcp::version),
885 dcp::LocalTime().as_string(),
890 Editor e (find_cpl(dir));
891 e.replace ("meta:Width", "meta:WidthX");
894 check_verify_result (
896 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
901 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
903 path const dir("build/test/verify_invalid_language1");
904 prepare_directory (dir);
905 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
906 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
907 asset->_language = "wrong-andbad";
908 asset->write (dir / "subs.mxf");
909 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
910 reel_asset->_language = "badlang";
911 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
913 check_verify_result (
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
917 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
918 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
923 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
924 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
926 path const dir("build/test/verify_invalid_language2");
927 prepare_directory (dir);
928 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
929 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
930 asset->_language = "wrong-andbad";
931 asset->write (dir / "subs.mxf");
932 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
933 reel_asset->_language = "badlang";
934 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
936 check_verify_result (
939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
940 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
941 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
946 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
947 * the release territory.
949 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
951 path const dir("build/test/verify_invalid_language3");
952 prepare_directory (dir);
954 auto picture = simple_picture (dir, "foo");
955 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
956 auto reel = make_shared<dcp::Reel>();
957 reel->add (reel_picture);
958 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
959 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
960 reel->add (reel_sound);
961 reel->add (simple_markers());
963 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
965 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
966 cpl->_additional_subtitle_languages.push_back("andso-is-this");
967 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
968 cpl->set_main_sound_sample_rate (48000);
969 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
970 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
971 cpl->set_version_number (1);
972 cpl->_release_territory = "fred-jim";
973 auto dcp = make_shared<dcp::DCP>(dir);
976 dcp::String::compose("libdcp %1", dcp::version),
977 dcp::String::compose("libdcp %1", dcp::version),
978 dcp::LocalTime().as_string(),
982 check_verify_result (
985 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
988 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
994 vector<dcp::VerificationNote>
995 check_picture_size (int width, int height, int frame_rate, bool three_d)
997 using namespace boost::filesystem;
999 path dcp_path = "build/test/verify_picture_test";
1000 prepare_directory (dcp_path);
1002 shared_ptr<dcp::PictureAsset> mp;
1004 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1006 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1008 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1010 auto image = black_image (dcp::Size(width, height));
1011 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1012 int const length = three_d ? frame_rate * 2 : frame_rate;
1013 for (int i = 0; i < length; ++i) {
1014 picture_writer->write (j2c.data(), j2c.size());
1016 picture_writer->finalize ();
1018 auto d = make_shared<dcp::DCP>(dcp_path);
1019 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1020 cpl->set_annotation_text ("A Test DCP");
1021 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1022 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1023 cpl->set_main_sound_sample_rate (48000);
1024 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1025 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1026 cpl->set_version_number (1);
1028 auto reel = make_shared<dcp::Reel>();
1031 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1033 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1036 reel->add (simple_markers(frame_rate));
1042 dcp::String::compose("libdcp %1", dcp::version),
1043 dcp::String::compose("libdcp %1", dcp::version),
1044 dcp::LocalTime().as_string(),
1048 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1054 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1056 auto notes = check_picture_size(width, height, frame_rate, three_d);
1057 BOOST_CHECK_EQUAL (notes.size(), 0U);
1063 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1065 auto notes = check_picture_size(width, height, frame_rate, three_d);
1066 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1067 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1068 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1074 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1076 auto notes = check_picture_size(width, height, frame_rate, three_d);
1077 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1078 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1079 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1085 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1087 auto notes = check_picture_size(width, height, frame_rate, three_d);
1088 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1089 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1090 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1094 BOOST_AUTO_TEST_CASE (verify_picture_size)
1096 using namespace boost::filesystem;
1099 check_picture_size_ok (2048, 858, 24, false);
1100 check_picture_size_ok (2048, 858, 25, false);
1101 check_picture_size_ok (2048, 858, 48, false);
1102 check_picture_size_ok (2048, 858, 24, true);
1103 check_picture_size_ok (2048, 858, 25, true);
1104 check_picture_size_ok (2048, 858, 48, true);
1107 check_picture_size_ok (1998, 1080, 24, false);
1108 check_picture_size_ok (1998, 1080, 25, false);
1109 check_picture_size_ok (1998, 1080, 48, false);
1110 check_picture_size_ok (1998, 1080, 24, true);
1111 check_picture_size_ok (1998, 1080, 25, true);
1112 check_picture_size_ok (1998, 1080, 48, true);
1115 check_picture_size_ok (4096, 1716, 24, false);
1118 check_picture_size_ok (3996, 2160, 24, false);
1120 /* Bad frame size */
1121 check_picture_size_bad_frame_size (2050, 858, 24, false);
1122 check_picture_size_bad_frame_size (2048, 658, 25, false);
1123 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1124 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1126 /* Bad 2K frame rate */
1127 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1128 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1129 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1131 /* Bad 4K frame rate */
1132 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1133 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1136 auto notes = check_picture_size(3996, 2160, 24, true);
1137 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1138 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1139 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1145 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1148 make_shared<dcp::SubtitleString>(
1156 dcp::Time(start_frame, 24, 24),
1157 dcp::Time(end_frame, 24, 24),
1159 dcp::HAlign::CENTER,
1161 dcp::VAlign::CENTER,
1162 dcp::Direction::LTR,
1173 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1175 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1176 prepare_directory (dir);
1178 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1179 for (int i = 0; i < 2048; ++i) {
1180 add_test_subtitle (asset, i * 24, i * 24 + 20);
1182 asset->set_language (dcp::LanguageTag("de-DE"));
1183 asset->write (dir / "subs.mxf");
1184 auto reel_asset = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1185 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1187 check_verify_result (
1190 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1192 dcp::VerificationNote::Type::BV21_ERROR,
1193 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1195 canonical(dir / "subs.mxf")
1197 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1198 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1204 shared_ptr<dcp::SMPTESubtitleAsset>
1205 make_large_subtitle_asset (path font_file)
1207 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1208 dcp::ArrayData big_fake_font(1024 * 1024);
1209 big_fake_font.write (font_file);
1210 for (int i = 0; i < 116; ++i) {
1211 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1219 verify_timed_text_asset_too_large (string name)
1221 auto const dir = path("build/test") / name;
1222 prepare_directory (dir);
1223 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1224 add_test_subtitle (asset, 0, 240);
1225 asset->set_language (dcp::LanguageTag("de-DE"));
1226 asset->write (dir / "subs.mxf");
1228 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1229 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1231 check_verify_result (
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1237 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1238 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1243 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1245 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1246 verify_timed_text_asset_too_large<dcp::ReelSMPTEClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1250 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1252 path dir = "build/test/verify_missing_subtitle_language";
1253 prepare_directory (dir);
1254 auto dcp = make_simple (dir, 1, 106);
1257 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1258 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1259 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1260 "<ContentTitleText>Content</ContentTitleText>"
1261 "<AnnotationText>Annotation</AnnotationText>"
1262 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1263 "<ReelNumber>1</ReelNumber>"
1264 "<EditRate>24 1</EditRate>"
1265 "<TimeCodeRate>24</TimeCodeRate>"
1266 "<StartTime>00:00:00:00</StartTime>"
1267 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1269 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1270 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1271 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1277 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1278 BOOST_REQUIRE (xml_file);
1279 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1281 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1282 subs->write (dir / "subs.mxf");
1284 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1285 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1287 dcp::String::compose("libdcp %1", dcp::version),
1288 dcp::String::compose("libdcp %1", dcp::version),
1289 dcp::LocalTime().as_string(),
1293 check_verify_result (
1296 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1297 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1302 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1304 path path ("build/test/verify_mismatched_subtitle_languages");
1305 auto constexpr reel_length = 192;
1306 auto dcp = make_simple (path, 2, reel_length);
1307 auto cpl = dcp->cpls()[0];
1310 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1311 subs->set_language (dcp::LanguageTag("de-DE"));
1312 subs->add (simple_subtitle());
1313 subs->write (path / "subs1.mxf");
1314 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1315 cpl->reels()[0]->add(reel_subs);
1319 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1320 subs->set_language (dcp::LanguageTag("en-US"));
1321 subs->add (simple_subtitle());
1322 subs->write (path / "subs2.mxf");
1323 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1324 cpl->reels()[1]->add(reel_subs);
1328 dcp::String::compose("libdcp %1", dcp::version),
1329 dcp::String::compose("libdcp %1", dcp::version),
1330 dcp::LocalTime().as_string(),
1334 check_verify_result (
1337 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1339 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1344 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1346 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1347 auto constexpr reel_length = 192;
1348 auto dcp = make_simple (path, 2, reel_length);
1349 auto cpl = dcp->cpls()[0];
1352 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1353 ccaps->set_language (dcp::LanguageTag("de-DE"));
1354 ccaps->add (simple_subtitle());
1355 ccaps->write (path / "subs1.mxf");
1356 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1357 cpl->reels()[0]->add(reel_ccaps);
1361 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1362 ccaps->set_language (dcp::LanguageTag("en-US"));
1363 ccaps->add (simple_subtitle());
1364 ccaps->write (path / "subs2.mxf");
1365 auto reel_ccaps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1366 cpl->reels()[1]->add(reel_ccaps);
1370 dcp::String::compose("libdcp %1", dcp::version),
1371 dcp::String::compose("libdcp %1", dcp::version),
1372 dcp::LocalTime().as_string(),
1376 check_verify_result (
1379 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1380 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1385 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1387 path dir = "build/test/verify_missing_subtitle_start_time";
1388 prepare_directory (dir);
1389 auto dcp = make_simple (dir, 1, 106);
1392 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1393 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1394 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1395 "<ContentTitleText>Content</ContentTitleText>"
1396 "<AnnotationText>Annotation</AnnotationText>"
1397 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1398 "<ReelNumber>1</ReelNumber>"
1399 "<Language>de-DE</Language>"
1400 "<EditRate>24 1</EditRate>"
1401 "<TimeCodeRate>24</TimeCodeRate>"
1402 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1404 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1405 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1406 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1412 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1413 BOOST_REQUIRE (xml_file);
1414 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1416 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1417 subs->write (dir / "subs.mxf");
1419 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1420 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1422 dcp::String::compose("libdcp %1", dcp::version),
1423 dcp::String::compose("libdcp %1", dcp::version),
1424 dcp::LocalTime().as_string(),
1428 check_verify_result (
1431 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1432 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1437 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1439 path dir = "build/test/verify_invalid_subtitle_start_time";
1440 prepare_directory (dir);
1441 auto dcp = make_simple (dir, 1, 106);
1444 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1445 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1446 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1447 "<ContentTitleText>Content</ContentTitleText>"
1448 "<AnnotationText>Annotation</AnnotationText>"
1449 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1450 "<ReelNumber>1</ReelNumber>"
1451 "<Language>de-DE</Language>"
1452 "<EditRate>24 1</EditRate>"
1453 "<TimeCodeRate>24</TimeCodeRate>"
1454 "<StartTime>00:00:02:00</StartTime>"
1455 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1457 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1458 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1459 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1465 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1466 BOOST_REQUIRE (xml_file);
1467 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1469 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1470 subs->write (dir / "subs.mxf");
1472 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1473 dcp->cpls().front()->reels().front()->add(reel_subs);
1475 dcp::String::compose("libdcp %1", dcp::version),
1476 dcp::String::compose("libdcp %1", dcp::version),
1477 dcp::LocalTime().as_string(),
1481 check_verify_result (
1484 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1485 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1493 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1496 , v_position(v_position_)
1508 shared_ptr<dcp::CPL>
1509 dcp_with_text (path dir, vector<TestText> subs)
1511 prepare_directory (dir);
1512 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1513 asset->set_start_time (dcp::Time());
1514 for (auto i: subs) {
1515 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1517 asset->set_language (dcp::LanguageTag("de-DE"));
1518 asset->write (dir / "subs.mxf");
1520 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1521 return write_dcp_with_single_asset (dir, reel_asset);
1525 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1527 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1528 /* Just too early */
1529 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1530 check_verify_result (
1533 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1540 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1542 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1543 /* Just late enough */
1544 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1545 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1549 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1551 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1552 prepare_directory (dir);
1554 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1555 asset1->set_start_time (dcp::Time());
1556 /* Just late enough */
1557 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1558 asset1->set_language (dcp::LanguageTag("de-DE"));
1559 asset1->write (dir / "subs1.mxf");
1560 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1561 auto reel1 = make_shared<dcp::Reel>();
1562 reel1->add (reel_asset1);
1563 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1564 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1565 reel1->add (markers1);
1567 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1568 asset2->set_start_time (dcp::Time());
1569 /* This would be too early on first reel but should be OK on the second */
1570 add_test_subtitle (asset2, 3, 4 * 24);
1571 asset2->set_language (dcp::LanguageTag("de-DE"));
1572 asset2->write (dir / "subs2.mxf");
1573 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1574 auto reel2 = make_shared<dcp::Reel>();
1575 reel2->add (reel_asset2);
1576 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1577 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1578 reel2->add (markers2);
1580 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1583 auto dcp = make_shared<dcp::DCP>(dir);
1586 dcp::String::compose("libdcp %1", dcp::version),
1587 dcp::String::compose("libdcp %1", dcp::version),
1588 dcp::LocalTime().as_string(),
1593 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1597 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1599 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1600 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1604 { 5 * 24 + 1, 6 * 24 },
1606 check_verify_result (
1609 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1610 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1615 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1617 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1618 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1622 { 5 * 24 + 16, 8 * 24 },
1624 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1628 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1630 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1631 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1632 check_verify_result (
1635 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1636 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1641 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1643 auto const dir = path("build/test/verify_valid_subtitle_duration");
1644 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1645 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1649 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1651 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1652 prepare_directory (dir);
1653 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1654 asset->set_start_time (dcp::Time());
1655 add_test_subtitle (asset, 0, 4 * 24);
1656 asset->set_language (dcp::LanguageTag("de-DE"));
1657 asset->write (dir / "subs.mxf");
1659 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1660 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1661 check_verify_result (
1664 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1665 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1666 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1667 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1673 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1675 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1676 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1679 { 96, 200, 0.0, "We" },
1680 { 96, 200, 0.1, "have" },
1681 { 96, 200, 0.2, "four" },
1682 { 96, 200, 0.3, "lines" }
1684 check_verify_result (
1687 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1688 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1693 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1695 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1696 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1699 { 96, 200, 0.0, "We" },
1700 { 96, 200, 0.1, "have" },
1701 { 96, 200, 0.2, "four" },
1703 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1707 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1709 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1710 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1713 { 96, 300, 0.0, "We" },
1714 { 96, 300, 0.1, "have" },
1715 { 150, 180, 0.2, "four" },
1716 { 150, 180, 0.3, "lines" }
1718 check_verify_result (
1721 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1722 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1727 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1729 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1730 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1733 { 96, 300, 0.0, "We" },
1734 { 96, 300, 0.1, "have" },
1735 { 150, 180, 0.2, "four" },
1736 { 190, 250, 0.3, "lines" }
1738 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1742 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1744 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1745 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1748 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1750 check_verify_result (
1753 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1754 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1759 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1761 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1762 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1765 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1767 check_verify_result (
1770 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1771 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1776 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1778 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1779 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1782 { 96, 200, 0.0, "We" },
1783 { 96, 200, 0.1, "have" },
1784 { 96, 200, 0.2, "four" },
1785 { 96, 200, 0.3, "lines" }
1787 check_verify_result (
1790 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1791 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1796 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1798 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1799 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1802 { 96, 200, 0.0, "We" },
1803 { 96, 200, 0.1, "have" },
1804 { 96, 200, 0.2, "four" },
1806 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1810 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1812 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1813 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1816 { 96, 300, 0.0, "We" },
1817 { 96, 300, 0.1, "have" },
1818 { 150, 180, 0.2, "four" },
1819 { 150, 180, 0.3, "lines" }
1821 check_verify_result (
1824 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1825 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1830 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1832 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1833 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1836 { 96, 300, 0.0, "We" },
1837 { 96, 300, 0.1, "have" },
1838 { 150, 180, 0.2, "four" },
1839 { 190, 250, 0.3, "lines" }
1841 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1845 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1847 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1848 auto cpl = dcp_with_text<dcp::ReelSMPTEClosedCaptionAsset> (
1851 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1853 check_verify_result (
1856 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1862 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1864 path const dir("build/test/verify_invalid_sound_frame_rate");
1865 prepare_directory (dir);
1867 auto picture = simple_picture (dir, "foo");
1868 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1869 auto reel = make_shared<dcp::Reel>();
1870 reel->add (reel_picture);
1871 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1872 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1873 reel->add (reel_sound);
1874 reel->add (simple_markers());
1875 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1877 auto dcp = make_shared<dcp::DCP>(dir);
1880 dcp::String::compose("libdcp %1", dcp::version),
1881 dcp::String::compose("libdcp %1", dcp::version),
1882 dcp::LocalTime().as_string(),
1886 check_verify_result (
1889 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1890 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1895 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1897 path const dir("build/test/verify_missing_cpl_annotation_text");
1898 auto dcp = make_simple (dir);
1900 dcp::String::compose("libdcp %1", dcp::version),
1901 dcp::String::compose("libdcp %1", dcp::version),
1902 dcp::LocalTime().as_string(),
1906 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1908 auto const cpl = dcp->cpls()[0];
1911 BOOST_REQUIRE (cpl->file());
1912 Editor e(cpl->file().get());
1913 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1916 check_verify_result (
1919 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1920 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1925 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1927 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1928 auto dcp = make_simple (dir);
1930 dcp::String::compose("libdcp %1", dcp::version),
1931 dcp::String::compose("libdcp %1", dcp::version),
1932 dcp::LocalTime().as_string(),
1936 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1937 auto const cpl = dcp->cpls()[0];
1940 BOOST_REQUIRE (cpl->file());
1941 Editor e(cpl->file().get());
1942 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1945 check_verify_result (
1948 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1949 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1954 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1956 path const dir("build/test/verify_mismatched_asset_duration");
1957 prepare_directory (dir);
1958 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1959 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1961 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1962 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1964 auto reel = make_shared<dcp::Reel>(
1965 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1966 make_shared<dcp::ReelSoundAsset>(ms, 0)
1969 reel->add (simple_markers());
1974 dcp::String::compose("libdcp %1", dcp::version),
1975 dcp::String::compose("libdcp %1", dcp::version),
1976 dcp::LocalTime().as_string(),
1980 check_verify_result (
1983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1991 shared_ptr<dcp::CPL>
1992 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1994 prepare_directory (dir);
1995 auto dcp = make_shared<dcp::DCP>(dir);
1996 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1998 auto constexpr reel_length = 192;
2000 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2001 subs->set_language (dcp::LanguageTag("de-DE"));
2002 subs->set_start_time (dcp::Time());
2003 subs->add (simple_subtitle());
2004 subs->write (dir / "subs.mxf");
2005 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2007 auto reel1 = make_shared<dcp::Reel>(
2008 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2009 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2013 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2016 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2017 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2018 reel1->add (markers1);
2022 auto reel2 = make_shared<dcp::Reel>(
2023 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2024 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2028 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2031 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2032 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2033 reel2->add (markers2);
2039 dcp::String::compose("libdcp %1", dcp::version),
2040 dcp::String::compose("libdcp %1", dcp::version),
2041 dcp::LocalTime().as_string(),
2049 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2052 path dir ("build/test/missing_main_subtitle_from_some_reels");
2053 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2054 check_verify_result (
2057 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2058 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2064 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2065 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2066 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2070 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2071 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2072 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2078 shared_ptr<dcp::CPL>
2079 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2081 prepare_directory (dir);
2082 auto dcp = make_shared<dcp::DCP>(dir);
2083 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2085 auto constexpr reel_length = 192;
2087 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2088 subs->set_language (dcp::LanguageTag("de-DE"));
2089 subs->set_start_time (dcp::Time());
2090 subs->add (simple_subtitle());
2091 subs->write (dir / "subs.mxf");
2093 auto reel1 = make_shared<dcp::Reel>(
2094 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2095 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2098 for (int i = 0; i < caps_in_reel1; ++i) {
2099 reel1->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2102 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2103 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2104 reel1->add (markers1);
2108 auto reel2 = make_shared<dcp::Reel>(
2109 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2110 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2113 for (int i = 0; i < caps_in_reel2; ++i) {
2114 reel2->add (make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2117 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2118 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2119 reel2->add (markers2);
2125 dcp::String::compose("libdcp %1", dcp::version),
2126 dcp::String::compose("libdcp %1", dcp::version),
2127 dcp::LocalTime().as_string(),
2135 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2138 path dir ("build/test/mismatched_closed_caption_asset_counts");
2139 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2140 check_verify_result (
2143 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2144 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2149 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2150 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2151 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2155 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2156 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2157 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2164 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2166 prepare_directory (dir);
2167 auto dcp = make_shared<dcp::DCP>(dir);
2168 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2170 auto constexpr reel_length = 192;
2172 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2173 subs->set_language (dcp::LanguageTag("de-DE"));
2174 subs->set_start_time (dcp::Time());
2175 subs->add (simple_subtitle());
2176 subs->write (dir / "subs.mxf");
2177 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2180 auto reel = make_shared<dcp::Reel>(
2181 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2182 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2185 reel->add (reel_text);
2187 reel->add (simple_markers(reel_length));
2193 dcp::String::compose("libdcp %1", dcp::version),
2194 dcp::String::compose("libdcp %1", dcp::version),
2195 dcp::LocalTime().as_string(),
2199 check_verify_result (
2202 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2203 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2208 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2210 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2211 "build/test/verify_subtitle_entry_point_must_be_present",
2212 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2213 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2214 asset->unset_entry_point ();
2218 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2219 "build/test/verify_subtitle_entry_point_must_be_zero",
2220 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2221 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2222 asset->set_entry_point (4);
2226 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2227 "build/test/verify_closed_caption_entry_point_must_be_present",
2228 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2229 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2230 asset->unset_entry_point ();
2234 verify_text_entry_point_check<dcp::ReelSMPTEClosedCaptionAsset> (
2235 "build/test/verify_closed_caption_entry_point_must_be_zero",
2236 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2237 [](shared_ptr<dcp::ReelSMPTEClosedCaptionAsset> asset) {
2238 asset->set_entry_point (9);
2244 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2248 path const dir("build/test/verify_missing_hash");
2249 auto dcp = make_simple (dir);
2251 dcp::String::compose("libdcp %1", dcp::version),
2252 dcp::String::compose("libdcp %1", dcp::version),
2253 dcp::LocalTime().as_string(),
2257 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2258 auto const cpl = dcp->cpls()[0];
2261 BOOST_REQUIRE (cpl->file());
2262 Editor e(cpl->file().get());
2263 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2266 check_verify_result (
2269 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2270 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2277 verify_markers_test (
2279 vector<pair<dcp::Marker, dcp::Time>> markers,
2280 vector<dcp::VerificationNote> test_notes
2283 auto dcp = make_simple (dir);
2284 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2285 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2286 for (auto const& i: markers) {
2287 markers_asset->set (i.first, i.second);
2289 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2291 dcp::String::compose("libdcp %1", dcp::version),
2292 dcp::String::compose("libdcp %1", dcp::version),
2293 dcp::LocalTime().as_string(),
2297 check_verify_result ({dir}, test_notes);
2301 BOOST_AUTO_TEST_CASE (verify_markers)
2303 verify_markers_test (
2304 "build/test/verify_markers_all_correct",
2306 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2307 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2308 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2309 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2314 verify_markers_test (
2315 "build/test/verify_markers_missing_ffec",
2317 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2318 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2319 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2322 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2325 verify_markers_test (
2326 "build/test/verify_markers_missing_ffmc",
2328 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2329 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2330 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2336 verify_markers_test (
2337 "build/test/verify_markers_missing_ffoc",
2339 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2340 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2341 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2344 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2347 verify_markers_test (
2348 "build/test/verify_markers_missing_lfoc",
2350 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2351 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2352 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2355 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2358 verify_markers_test (
2359 "build/test/verify_markers_incorrect_ffoc",
2361 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2362 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2363 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2364 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2367 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2370 verify_markers_test (
2371 "build/test/verify_markers_incorrect_lfoc",
2373 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2374 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2375 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2376 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2379 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2384 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2386 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2387 prepare_directory (dir);
2388 auto dcp = make_simple (dir);
2389 auto cpl = dcp->cpls()[0];
2390 cpl->unset_version_number();
2392 dcp::String::compose("libdcp %1", dcp::version),
2393 dcp::String::compose("libdcp %1", dcp::version),
2394 dcp::LocalTime().as_string(),
2398 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2402 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2404 path dir = "build/test/verify_missing_extension_metadata1";
2405 auto dcp = make_simple (dir);
2407 dcp::String::compose("libdcp %1", dcp::version),
2408 dcp::String::compose("libdcp %1", dcp::version),
2409 dcp::LocalTime().as_string(),
2413 auto cpl = dcp->cpls()[0];
2416 Editor e (cpl->file().get());
2417 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2420 check_verify_result (
2423 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2429 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2431 path dir = "build/test/verify_missing_extension_metadata2";
2432 auto dcp = make_simple (dir);
2434 dcp::String::compose("libdcp %1", dcp::version),
2435 dcp::String::compose("libdcp %1", dcp::version),
2436 dcp::LocalTime().as_string(),
2440 auto cpl = dcp->cpls()[0];
2443 Editor e (cpl->file().get());
2444 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2447 check_verify_result (
2450 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2451 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2456 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2458 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2459 auto dcp = make_simple (dir);
2461 dcp::String::compose("libdcp %1", dcp::version),
2462 dcp::String::compose("libdcp %1", dcp::version),
2463 dcp::LocalTime().as_string(),
2467 auto const cpl = dcp->cpls()[0];
2470 Editor e (cpl->file().get());
2471 e.replace ("<meta:Name>A", "<meta:NameX>A");
2472 e.replace ("n</meta:Name>", "n</meta:NameX>");
2475 check_verify_result (
2478 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2479 { 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 },
2480 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2485 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2487 path dir = "build/test/verify_invalid_extension_metadata1";
2488 auto dcp = make_simple (dir);
2490 dcp::String::compose("libdcp %1", dcp::version),
2491 dcp::String::compose("libdcp %1", dcp::version),
2492 dcp::LocalTime().as_string(),
2496 auto cpl = dcp->cpls()[0];
2499 Editor e (cpl->file().get());
2500 e.replace ("Application", "Fred");
2503 check_verify_result (
2506 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2507 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2512 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2514 path dir = "build/test/verify_invalid_extension_metadata2";
2515 auto dcp = make_simple (dir);
2517 dcp::String::compose("libdcp %1", dcp::version),
2518 dcp::String::compose("libdcp %1", dcp::version),
2519 dcp::LocalTime().as_string(),
2523 auto cpl = dcp->cpls()[0];
2526 Editor e (cpl->file().get());
2527 e.replace ("DCP Constraints Profile", "Fred");
2530 check_verify_result (
2533 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2539 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2541 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2542 auto dcp = make_simple (dir);
2544 dcp::String::compose("libdcp %1", dcp::version),
2545 dcp::String::compose("libdcp %1", dcp::version),
2546 dcp::LocalTime().as_string(),
2550 auto const cpl = dcp->cpls()[0];
2553 Editor e (cpl->file().get());
2554 e.replace ("<meta:Value>", "<meta:ValueX>");
2555 e.replace ("</meta:Value>", "</meta:ValueX>");
2558 check_verify_result (
2561 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2562 { 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 },
2563 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2568 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2570 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2571 auto dcp = make_simple (dir);
2573 dcp::String::compose("libdcp %1", dcp::version),
2574 dcp::String::compose("libdcp %1", dcp::version),
2575 dcp::LocalTime().as_string(),
2579 auto const cpl = dcp->cpls()[0];
2582 Editor e (cpl->file().get());
2583 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2586 check_verify_result (
2589 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2590 { 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() },
2595 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2597 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2598 auto dcp = make_simple (dir);
2600 dcp::String::compose("libdcp %1", dcp::version),
2601 dcp::String::compose("libdcp %1", dcp::version),
2602 dcp::LocalTime().as_string(),
2606 auto const cpl = dcp->cpls()[0];
2609 Editor e (cpl->file().get());
2610 e.replace ("<meta:Property>", "<meta:PropertyX>");
2611 e.replace ("</meta:Property>", "</meta:PropertyX>");
2614 check_verify_result (
2617 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2618 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2619 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2624 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2626 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2627 auto dcp = make_simple (dir);
2629 dcp::String::compose("libdcp %1", dcp::version),
2630 dcp::String::compose("libdcp %1", dcp::version),
2631 dcp::LocalTime().as_string(),
2635 auto const cpl = dcp->cpls()[0];
2638 Editor e (cpl->file().get());
2639 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2640 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2643 check_verify_result (
2646 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2647 { 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 },
2648 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2654 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2656 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2657 prepare_directory (dir);
2658 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2659 copy_file (i.path(), dir / i.path().filename());
2662 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2663 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2667 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2670 check_verify_result (
2673 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2677 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2678 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2679 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2680 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2685 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2687 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2688 prepare_directory (dir);
2689 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2690 copy_file (i.path(), dir / i.path().filename());
2693 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2694 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2697 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2700 check_verify_result (
2703 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2704 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2706 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2707 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2708 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2709 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2714 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2716 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2717 prepare_directory (dir);
2718 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2719 copy_file (i.path(), dir / i.path().filename());
2723 Editor e (dir / dcp_test1_pkl);
2724 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2727 check_verify_result ({dir}, {});
2731 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2733 path dir ("build/test/verify_must_not_be_partially_encrypted");
2734 prepare_directory (dir);
2738 auto signer = make_shared<dcp::CertificateChain>();
2739 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2740 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2741 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2742 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2744 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2748 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2751 auto writer = mp->start_write (dir / "video.mxf", false);
2752 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2753 for (int i = 0; i < 24; ++i) {
2754 writer->write (j2c.data(), j2c.size());
2756 writer->finalize ();
2758 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2760 auto reel = make_shared<dcp::Reel>(
2761 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2762 make_shared<dcp::ReelSoundAsset>(ms, 0)
2765 reel->add (simple_markers());
2769 cpl->set_content_version (
2770 {"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"}
2772 cpl->set_annotation_text ("A Test DCP");
2773 cpl->set_issuer ("OpenDCP 0.0.25");
2774 cpl->set_creator ("OpenDCP 0.0.25");
2775 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2776 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2777 cpl->set_main_sound_sample_rate (48000);
2778 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2779 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2780 cpl->set_version_number (1);
2784 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2786 check_verify_result (
2789 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2794 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2796 vector<dcp::VerificationNote> notes;
2797 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"));
2798 auto reader = picture.start_read ();
2799 auto frame = reader->get_frame (0);
2800 verify_j2k (frame, notes);
2801 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2805 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2807 vector<dcp::VerificationNote> notes;
2808 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2809 auto reader = picture.start_read ();
2810 auto frame = reader->get_frame (0);
2811 verify_j2k (frame, notes);
2812 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2816 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2818 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2819 prepare_directory (dir);
2820 auto dcp = make_simple (dir);
2822 vector<dcp::VerificationNote> notes;
2823 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2824 auto reader = picture.start_read ();
2825 auto frame = reader->get_frame (0);
2826 verify_j2k (frame, notes);
2827 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2831 /** Check that ResourceID and the XML ID being different is spotted */
2832 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2834 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2835 prepare_directory (dir);
2837 ASDCP::WriterInfo writer_info;
2838 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2841 auto mxf_id = dcp::make_uuid ();
2842 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2843 BOOST_REQUIRE (c == Kumu::UUID_Length);
2845 auto resource_id = dcp::make_uuid ();
2846 ASDCP::TimedText::TimedTextDescriptor descriptor;
2847 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2848 DCP_ASSERT (c == Kumu::UUID_Length);
2850 auto xml_id = dcp::make_uuid ();
2851 ASDCP::TimedText::MXFWriter writer;
2852 auto subs_mxf = dir / "subs.mxf";
2853 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2854 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2855 writer.WriteTimedTextResource (dcp::String::compose(
2856 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2857 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2858 "<Id>urn:uuid:%1</Id>"
2859 "<ContentTitleText>Content</ContentTitleText>"
2860 "<AnnotationText>Annotation</AnnotationText>"
2861 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2862 "<ReelNumber>1</ReelNumber>"
2863 "<Language>en-US</Language>"
2864 "<EditRate>25 1</EditRate>"
2865 "<TimeCodeRate>25</TimeCodeRate>"
2866 "<StartTime>00:00:00:00</StartTime>"
2868 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2869 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2870 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2879 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2880 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2882 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2884 check_verify_result (
2887 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2889 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2890 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2895 /** Check that ResourceID and the MXF ID being the same is spotted */
2896 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2898 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2899 prepare_directory (dir);
2901 ASDCP::WriterInfo writer_info;
2902 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2905 auto mxf_id = dcp::make_uuid ();
2906 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2907 BOOST_REQUIRE (c == Kumu::UUID_Length);
2909 auto resource_id = mxf_id;
2910 ASDCP::TimedText::TimedTextDescriptor descriptor;
2911 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2912 DCP_ASSERT (c == Kumu::UUID_Length);
2914 auto xml_id = resource_id;
2915 ASDCP::TimedText::MXFWriter writer;
2916 auto subs_mxf = dir / "subs.mxf";
2917 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2918 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2919 writer.WriteTimedTextResource (dcp::String::compose(
2920 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2921 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2922 "<Id>urn:uuid:%1</Id>"
2923 "<ContentTitleText>Content</ContentTitleText>"
2924 "<AnnotationText>Annotation</AnnotationText>"
2925 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2926 "<ReelNumber>1</ReelNumber>"
2927 "<Language>en-US</Language>"
2928 "<EditRate>25 1</EditRate>"
2929 "<TimeCodeRate>25</TimeCodeRate>"
2930 "<StartTime>00:00:00:00</StartTime>"
2932 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2933 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2934 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2943 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2944 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2946 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2948 check_verify_result (
2951 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2952 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2953 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2954 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2959 /** Check a DCP with a 3D asset marked as 2D */
2960 BOOST_AUTO_TEST_CASE (verify_threed_marked_as_twod)
2962 check_verify_result (
2963 { private_test / "data" / "xm" },
2966 dcp::VerificationNote::Type::WARNING,
2967 dcp::VerificationNote::Code::THREED_ASSET_MARKED_AS_TWOD, boost::filesystem::canonical(find_file(private_test / "data" / "xm", "j2c"))
2970 dcp::VerificationNote::Type::BV21_ERROR,
2971 dcp::VerificationNote::Code::INVALID_STANDARD