2 Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "compose.hpp"
37 #include "interop_subtitle_asset.h"
38 #include "j2k_transcode.h"
39 #include "mono_picture_asset.h"
40 #include "mono_picture_asset_writer.h"
41 #include "openjpeg_image.h"
42 #include "raw_convert.h"
44 #include "reel_closed_caption_asset.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_smpte_subtitle_asset.h"
51 #include "smpte_subtitle_asset.h"
52 #include "stereo_picture_asset.h"
53 #include "stream_operators.h"
57 #include "verify_j2k.h"
58 #include <boost/test/unit_test.hpp>
59 #include <boost/algorithm/string.hpp>
68 using std::make_shared;
69 using boost::optional;
70 using namespace boost::filesystem;
71 using std::shared_ptr;
74 static list<pair<string, optional<path>>> stages;
75 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
76 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
77 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
78 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
79 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
80 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
81 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
84 stage (string s, optional<path> p)
86 stages.push_back (make_pair (s, p));
96 prepare_directory (path path)
98 using namespace boost::filesystem;
100 create_directories (path);
105 setup (int reference_number, string verify_test_suffix)
107 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
108 prepare_directory (dir);
109 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
110 copy_file (i.path(), dir / i.path().filename());
119 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
121 auto reel = make_shared<dcp::Reel>();
122 reel->add (reel_asset);
123 reel->add (simple_markers());
125 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
127 auto dcp = make_shared<dcp::DCP>(dir);
131 dcp::String::compose("libdcp %1", dcp::version),
132 dcp::String::compose("libdcp %1", dcp::version),
133 dcp::LocalTime().as_string(),
141 /** Class that can alter a file by searching and replacing strings within it.
142 * On destruction modifies the file whose name was given to the constructor.
150 _content = dcp::file_to_string (_path);
155 auto f = fopen(_path.string().c_str(), "w");
157 fwrite (_content.c_str(), _content.length(), 1, f);
161 void replace (string a, string b)
163 auto old_content = _content;
164 boost::algorithm::replace_all (_content, a, b);
165 BOOST_REQUIRE (_content != old_content);
168 void delete_lines (string from, string to)
170 vector<string> lines;
171 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
172 bool deleting = false;
173 auto old_content = _content;
175 for (auto i: lines) {
176 if (i.find(from) != string::npos) {
180 _content += i + "\n";
182 if (deleting && i.find(to) != string::npos) {
186 BOOST_REQUIRE (_content != old_content);
191 std::string _content;
198 dump_notes (vector<dcp::VerificationNote> const & notes)
200 for (auto i: notes) {
201 std::cout << dcp::note_to_string(i) << "\n";
209 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
211 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
212 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
213 for (auto i = 0U; i < notes.size(); ++i) {
214 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
221 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
223 auto dir = setup (1, suffix);
226 Editor e (file(suffix));
227 e.replace (from, to);
230 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
232 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
233 auto i = notes.begin();
234 auto j = codes.begin();
235 while (i != notes.end()) {
236 BOOST_CHECK_EQUAL (i->code(), *j);
243 BOOST_AUTO_TEST_CASE (verify_no_error)
246 auto dir = setup (1, "no_error");
247 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
249 path const cpl_file = dir / dcp_test1_cpl;
250 path const pkl_file = dir / dcp_test1_pkl;
251 path const assetmap_file = dir / "ASSETMAP.xml";
253 auto st = stages.begin();
254 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
255 BOOST_REQUIRE (st->second);
256 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
258 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
259 BOOST_REQUIRE (st->second);
260 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
262 BOOST_CHECK_EQUAL (st->first, "Checking reel");
263 BOOST_REQUIRE (!st->second);
265 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
266 BOOST_REQUIRE (st->second);
267 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
269 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
270 BOOST_REQUIRE (st->second);
271 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
273 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
274 BOOST_REQUIRE (st->second);
275 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
277 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
278 BOOST_REQUIRE (st->second);
279 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
281 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
282 BOOST_REQUIRE (st->second);
283 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
285 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
286 BOOST_REQUIRE (st->second);
287 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
289 BOOST_REQUIRE (st == stages.end());
291 BOOST_CHECK_EQUAL (notes.size(), 0);
295 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
297 using namespace boost::filesystem;
299 auto dir = setup (1, "incorrect_picture_sound_hash");
301 auto video_path = path(dir / "video.mxf");
302 auto mod = fopen(video_path.string().c_str(), "r+b");
304 fseek (mod, 4096, SEEK_SET);
306 fwrite (&x, sizeof(x), 1, mod);
309 auto audio_path = path(dir / "audio.mxf");
310 mod = fopen(audio_path.string().c_str(), "r+b");
312 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
313 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
316 dcp::ASDCPErrorSuspender sus;
317 check_verify_result (
320 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
321 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
326 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
328 using namespace boost::filesystem;
330 auto dir = setup (1, "mismatched_picture_sound_hashes");
333 Editor e (dir / dcp_test1_pkl);
334 e.replace ("<Hash>", "<Hash>x");
337 check_verify_result (
340 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
341 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
342 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
343 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xSEEi70vx1WQs67bmu2zKvzIkXvY=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
344 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
345 { 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 }
350 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
352 auto dir = setup (1, "failed_read_content_kind");
355 Editor e (dir / dcp_test1_cpl);
356 e.replace ("<ContentKind>", "<ContentKind>x");
359 check_verify_result (
361 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
370 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
378 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
384 asset_map (string suffix)
386 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
390 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
392 check_verify_result_after_replace (
393 "invalid_picture_frame_rate", &cpl,
394 "<FrameRate>24 1", "<FrameRate>99 1",
395 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
396 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
400 BOOST_AUTO_TEST_CASE (verify_missing_asset)
402 auto dir = setup (1, "missing_asset");
403 remove (dir / "video.mxf");
404 check_verify_result (
407 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
412 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
414 check_verify_result_after_replace (
415 "empty_asset_path", &asset_map,
416 "<Path>video.mxf</Path>", "<Path></Path>",
417 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
422 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
424 check_verify_result_after_replace (
425 "mismatched_standard", &cpl,
426 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
427 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
428 dcp::VerificationNote::Code::INVALID_XML,
429 dcp::VerificationNote::Code::INVALID_XML,
430 dcp::VerificationNote::Code::INVALID_XML,
431 dcp::VerificationNote::Code::INVALID_XML,
432 dcp::VerificationNote::Code::INVALID_XML,
433 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
438 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
440 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
441 check_verify_result_after_replace (
442 "invalid_xml_cpl_id", &cpl,
443 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
444 { dcp::VerificationNote::Code::INVALID_XML }
449 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
451 check_verify_result_after_replace (
452 "invalid_xml_issue_date", &cpl,
453 "<IssueDate>", "<IssueDate>x",
454 { dcp::VerificationNote::Code::INVALID_XML,
455 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
460 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
462 check_verify_result_after_replace (
463 "invalid_xml_pkl_id", &pkl,
464 "<Id>urn:uuid:" + dcp_test1_pkl_id.substr(0, 3),
465 "<Id>urn:uuid:x" + dcp_test1_pkl_id.substr(1, 2),
466 { dcp::VerificationNote::Code::INVALID_XML }
471 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
473 check_verify_result_after_replace (
474 "invalix_xml_asset_map_id", &asset_map,
475 "<Id>urn:uuid:" + dcp_test1_asset_map_id.substr(0, 3),
476 "<Id>urn:uuid:x" + dcp_test1_asset_map_id.substr(1, 2),
477 { dcp::VerificationNote::Code::INVALID_XML }
482 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
485 auto dir = setup (3, "verify_invalid_standard");
486 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
488 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
489 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
490 path const assetmap_file = dir / "ASSETMAP";
492 auto st = stages.begin();
493 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
494 BOOST_REQUIRE (st->second);
495 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
497 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
498 BOOST_REQUIRE (st->second);
499 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
501 BOOST_CHECK_EQUAL (st->first, "Checking reel");
502 BOOST_REQUIRE (!st->second);
504 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
505 BOOST_REQUIRE (st->second);
506 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
508 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
509 BOOST_REQUIRE (st->second);
510 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
512 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
513 BOOST_REQUIRE (st->second);
514 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
516 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
517 BOOST_REQUIRE (st->second);
518 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
520 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
521 BOOST_REQUIRE (st->second);
522 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
524 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
525 BOOST_REQUIRE (st->second);
526 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
528 BOOST_REQUIRE (st == stages.end());
530 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
531 auto i = notes.begin ();
532 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
533 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
535 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
536 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K);
539 /* DCP with a short asset */
540 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
542 auto dir = setup (8, "invalid_duration");
543 check_verify_result (
546 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
547 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
548 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
549 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
550 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
551 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, string("2") }
558 dcp_from_frame (dcp::ArrayData const& frame, path dir)
560 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
561 create_directories (dir);
562 auto writer = asset->start_write (dir / "pic.mxf", true);
563 for (int i = 0; i < 24; ++i) {
564 writer->write (frame.data(), frame.size());
568 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
569 return write_dcp_with_single_asset (dir, reel_asset);
573 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
575 int const too_big = 1302083 * 2;
577 /* Compress a black image */
578 auto image = black_image ();
579 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
580 BOOST_REQUIRE (frame.size() < too_big);
582 /* Place it in a bigger block with some zero padding at the end */
583 dcp::ArrayData oversized_frame(too_big);
584 memcpy (oversized_frame.data(), frame.data(), frame.size());
585 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
587 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
588 prepare_directory (dir);
589 auto cpl = dcp_from_frame (oversized_frame, dir);
591 check_verify_result (
594 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
595 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
596 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
601 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
603 int const nearly_too_big = 1302083 * 0.98;
605 /* Compress a black image */
606 auto image = black_image ();
607 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
608 BOOST_REQUIRE (frame.size() < nearly_too_big);
610 /* Place it in a bigger block with some zero padding at the end */
611 dcp::ArrayData oversized_frame(nearly_too_big);
612 memcpy (oversized_frame.data(), frame.data(), frame.size());
613 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
615 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
616 prepare_directory (dir);
617 auto cpl = dcp_from_frame (oversized_frame, dir);
619 check_verify_result (
622 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string("missing marker start byte") },
623 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
624 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
629 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
631 /* Compress a black image */
632 auto image = black_image ();
633 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
634 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
636 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
637 prepare_directory (dir);
638 auto cpl = dcp_from_frame (frame, dir);
640 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
644 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
646 path const dir("build/test/verify_valid_interop_subtitles");
647 prepare_directory (dir);
648 copy_file ("test/data/subs1.xml", dir / "subs.xml");
649 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
650 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
651 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
653 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
657 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
659 using namespace boost::filesystem;
661 path const dir("build/test/verify_invalid_interop_subtitles");
662 prepare_directory (dir);
663 copy_file ("test/data/subs1.xml", dir / "subs.xml");
664 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
665 auto reel_asset = make_shared<dcp::ReelInteropSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
666 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
669 Editor e (dir / "subs.xml");
670 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
673 check_verify_result (
676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
677 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
679 dcp::VerificationNote::Type::ERROR,
680 dcp::VerificationNote::Code::INVALID_XML,
681 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
689 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
691 path const dir("build/test/verify_valid_smpte_subtitles");
692 prepare_directory (dir);
693 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
694 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
695 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
696 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
698 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
702 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
704 using namespace boost::filesystem;
706 path const dir("build/test/verify_invalid_smpte_subtitles");
707 prepare_directory (dir);
708 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
709 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
710 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
711 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
713 check_verify_result (
716 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
718 dcp::VerificationNote::Type::ERROR,
719 dcp::VerificationNote::Code::INVALID_XML,
720 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
724 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
725 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
730 BOOST_AUTO_TEST_CASE (verify_external_asset)
732 path const ov_dir("build/test/verify_external_asset");
733 prepare_directory (ov_dir);
735 auto image = black_image ();
736 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
737 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
738 dcp_from_frame (frame, ov_dir);
740 dcp::DCP ov (ov_dir);
743 path const vf_dir("build/test/verify_external_asset_vf");
744 prepare_directory (vf_dir);
746 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
747 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
749 check_verify_result (
752 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
753 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
758 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
760 path const dir("build/test/verify_valid_cpl_metadata");
761 prepare_directory (dir);
763 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
764 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
765 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
767 auto reel = make_shared<dcp::Reel>();
768 reel->add (reel_asset);
770 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
771 reel->add (simple_markers(16 * 24));
773 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
775 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
776 cpl->set_main_sound_sample_rate (48000);
777 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
778 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
779 cpl->set_version_number (1);
784 dcp::Standard::SMPTE,
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::String::compose("libdcp %1", dcp::version),
787 dcp::LocalTime().as_string(),
793 path find_cpl (path dir)
795 for (auto i: directory_iterator(dir)) {
796 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
801 BOOST_REQUIRE (false);
806 /* DCP with invalid CompositionMetadataAsset */
807 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
809 using namespace boost::filesystem;
811 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
812 prepare_directory (dir);
814 auto reel = make_shared<dcp::Reel>();
815 reel->add (black_picture_asset(dir));
816 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
818 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
819 cpl->set_main_sound_sample_rate (48000);
820 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
821 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
822 cpl->set_version_number (1);
824 reel->add (simple_markers());
829 dcp::Standard::SMPTE,
830 dcp::String::compose("libdcp %1", dcp::version),
831 dcp::String::compose("libdcp %1", dcp::version),
832 dcp::LocalTime().as_string(),
837 Editor e (find_cpl(dir));
838 e.replace ("MainSound", "MainSoundX");
841 check_verify_result (
844 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
845 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
847 dcp::VerificationNote::Type::ERROR,
848 dcp::VerificationNote::Code::INVALID_XML,
849 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
850 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
851 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
852 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
853 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
854 "ExtensionMetadataList?,)'"),
855 canonical(cpl->file().get()),
858 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
863 /* DCP with invalid CompositionMetadataAsset */
864 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
866 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
867 prepare_directory (dir);
869 auto reel = make_shared<dcp::Reel>();
870 reel->add (black_picture_asset(dir));
871 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
873 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
874 cpl->set_main_sound_sample_rate (48000);
875 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
876 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
881 dcp::Standard::SMPTE,
882 dcp::String::compose("libdcp %1", dcp::version),
883 dcp::String::compose("libdcp %1", dcp::version),
884 dcp::LocalTime().as_string(),
889 Editor e (find_cpl(dir));
890 e.replace ("meta:Width", "meta:WidthX");
893 check_verify_result (
895 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
900 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
902 path const dir("build/test/verify_invalid_language1");
903 prepare_directory (dir);
904 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
905 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
906 asset->_language = "wrong-andbad";
907 asset->write (dir / "subs.mxf");
908 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
909 reel_asset->_language = "badlang";
910 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
912 check_verify_result (
915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
917 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
922 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
923 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
925 path const dir("build/test/verify_invalid_language2");
926 prepare_directory (dir);
927 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
928 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
929 asset->_language = "wrong-andbad";
930 asset->write (dir / "subs.mxf");
931 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
932 reel_asset->_language = "badlang";
933 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
935 check_verify_result (
938 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
939 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
940 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
945 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
946 * the release territory.
948 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
950 path const dir("build/test/verify_invalid_language3");
951 prepare_directory (dir);
953 auto picture = simple_picture (dir, "foo");
954 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
955 auto reel = make_shared<dcp::Reel>();
956 reel->add (reel_picture);
957 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
958 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
959 reel->add (reel_sound);
960 reel->add (simple_markers());
962 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
964 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
965 cpl->_additional_subtitle_languages.push_back("andso-is-this");
966 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
967 cpl->set_main_sound_sample_rate (48000);
968 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
969 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
970 cpl->set_version_number (1);
971 cpl->_release_territory = "fred-jim";
972 auto dcp = make_shared<dcp::DCP>(dir);
975 dcp::Standard::SMPTE,
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);
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::Standard::SMPTE,
1043 dcp::String::compose("libdcp %1", dcp::version),
1044 dcp::String::compose("libdcp %1", dcp::version),
1045 dcp::LocalTime().as_string(),
1049 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1055 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1057 auto notes = check_picture_size(width, height, frame_rate, three_d);
1058 BOOST_CHECK_EQUAL (notes.size(), 0U);
1064 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1066 auto notes = check_picture_size(width, height, frame_rate, three_d);
1067 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1068 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1069 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1075 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1077 auto notes = check_picture_size(width, height, frame_rate, three_d);
1078 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1079 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1080 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1086 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1088 auto notes = check_picture_size(width, height, frame_rate, three_d);
1089 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1090 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1091 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1095 BOOST_AUTO_TEST_CASE (verify_picture_size)
1097 using namespace boost::filesystem;
1100 check_picture_size_ok (2048, 858, 24, false);
1101 check_picture_size_ok (2048, 858, 25, false);
1102 check_picture_size_ok (2048, 858, 48, false);
1103 check_picture_size_ok (2048, 858, 24, true);
1104 check_picture_size_ok (2048, 858, 25, true);
1105 check_picture_size_ok (2048, 858, 48, true);
1108 check_picture_size_ok (1998, 1080, 24, false);
1109 check_picture_size_ok (1998, 1080, 25, false);
1110 check_picture_size_ok (1998, 1080, 48, false);
1111 check_picture_size_ok (1998, 1080, 24, true);
1112 check_picture_size_ok (1998, 1080, 25, true);
1113 check_picture_size_ok (1998, 1080, 48, true);
1116 check_picture_size_ok (4096, 1716, 24, false);
1119 check_picture_size_ok (3996, 2160, 24, false);
1121 /* Bad frame size */
1122 check_picture_size_bad_frame_size (2050, 858, 24, false);
1123 check_picture_size_bad_frame_size (2048, 658, 25, false);
1124 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1125 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1127 /* Bad 2K frame rate */
1128 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1129 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1130 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1132 /* Bad 4K frame rate */
1133 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1134 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1137 auto notes = check_picture_size(3996, 2160, 24, true);
1138 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1139 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1140 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1146 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1149 make_shared<dcp::SubtitleString>(
1157 dcp::Time(start_frame, 24, 24),
1158 dcp::Time(end_frame, 24, 24),
1160 dcp::HAlign::CENTER,
1162 dcp::VAlign::CENTER,
1163 dcp::Direction::LTR,
1174 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1176 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1177 prepare_directory (dir);
1179 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1180 for (int i = 0; i < 2048; ++i) {
1181 add_test_subtitle (asset, i * 24, i * 24 + 20);
1183 asset->set_language (dcp::LanguageTag("de-DE"));
1184 asset->write (dir / "subs.mxf");
1185 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1186 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1188 check_verify_result (
1191 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1193 dcp::VerificationNote::Type::BV21_ERROR,
1194 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1196 canonical(dir / "subs.mxf")
1198 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1199 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1205 shared_ptr<dcp::SMPTESubtitleAsset>
1206 make_large_subtitle_asset (path font_file)
1208 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1209 dcp::ArrayData big_fake_font(1024 * 1024);
1210 big_fake_font.write (font_file);
1211 for (int i = 0; i < 116; ++i) {
1212 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1220 verify_timed_text_asset_too_large (string name)
1222 auto const dir = path("build/test") / name;
1223 prepare_directory (dir);
1224 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1225 add_test_subtitle (asset, 0, 240);
1226 asset->set_language (dcp::LanguageTag("de-DE"));
1227 asset->write (dir / "subs.mxf");
1229 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1230 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1232 check_verify_result (
1235 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1236 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1237 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1238 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1239 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1244 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1246 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1247 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1251 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1253 path dir = "build/test/verify_missing_subtitle_language";
1254 prepare_directory (dir);
1255 auto dcp = make_simple (dir, 1, 106);
1258 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1259 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1260 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1261 "<ContentTitleText>Content</ContentTitleText>"
1262 "<AnnotationText>Annotation</AnnotationText>"
1263 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1264 "<ReelNumber>1</ReelNumber>"
1265 "<EditRate>24 1</EditRate>"
1266 "<TimeCodeRate>24</TimeCodeRate>"
1267 "<StartTime>00:00:00:00</StartTime>"
1268 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1270 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1271 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1272 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1278 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1279 BOOST_REQUIRE (xml_file);
1280 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1282 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1283 subs->write (dir / "subs.mxf");
1285 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1286 dcp->cpls().front()->reels().front()->add(reel_subs);
1288 dcp::Standard::SMPTE,
1289 dcp::String::compose("libdcp %1", dcp::version),
1290 dcp::String::compose("libdcp %1", dcp::version),
1291 dcp::LocalTime().as_string(),
1295 check_verify_result (
1298 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1299 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1304 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1306 path path ("build/test/verify_mismatched_subtitle_languages");
1307 auto constexpr reel_length = 192;
1308 auto dcp = make_simple (path, 2, reel_length);
1309 auto cpl = dcp->cpls()[0];
1312 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1313 subs->set_language (dcp::LanguageTag("de-DE"));
1314 subs->add (simple_subtitle());
1315 subs->write (path / "subs1.mxf");
1316 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1317 cpl->reels()[0]->add(reel_subs);
1321 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1322 subs->set_language (dcp::LanguageTag("en-US"));
1323 subs->add (simple_subtitle());
1324 subs->write (path / "subs2.mxf");
1325 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1326 cpl->reels()[1]->add(reel_subs);
1330 dcp::Standard::SMPTE,
1331 dcp::String::compose("libdcp %1", dcp::version),
1332 dcp::String::compose("libdcp %1", dcp::version),
1333 dcp::LocalTime().as_string(),
1337 check_verify_result (
1340 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1341 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1342 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1347 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1349 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1350 auto constexpr reel_length = 192;
1351 auto dcp = make_simple (path, 2, reel_length);
1352 auto cpl = dcp->cpls()[0];
1355 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1356 ccaps->set_language (dcp::LanguageTag("de-DE"));
1357 ccaps->add (simple_subtitle());
1358 ccaps->write (path / "subs1.mxf");
1359 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1360 cpl->reels()[0]->add(reel_ccaps);
1364 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1365 ccaps->set_language (dcp::LanguageTag("en-US"));
1366 ccaps->add (simple_subtitle());
1367 ccaps->write (path / "subs2.mxf");
1368 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1369 cpl->reels()[1]->add(reel_ccaps);
1373 dcp::Standard::SMPTE,
1374 dcp::String::compose("libdcp %1", dcp::version),
1375 dcp::String::compose("libdcp %1", dcp::version),
1376 dcp::LocalTime().as_string(),
1380 check_verify_result (
1383 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1384 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1389 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1391 path dir = "build/test/verify_missing_subtitle_start_time";
1392 prepare_directory (dir);
1393 auto dcp = make_simple (dir, 1, 106);
1396 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1397 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1398 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1399 "<ContentTitleText>Content</ContentTitleText>"
1400 "<AnnotationText>Annotation</AnnotationText>"
1401 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1402 "<ReelNumber>1</ReelNumber>"
1403 "<Language>de-DE</Language>"
1404 "<EditRate>24 1</EditRate>"
1405 "<TimeCodeRate>24</TimeCodeRate>"
1406 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1408 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1409 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1410 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1416 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1417 BOOST_REQUIRE (xml_file);
1418 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1420 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1421 subs->write (dir / "subs.mxf");
1423 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1424 dcp->cpls().front()->reels().front()->add(reel_subs);
1426 dcp::Standard::SMPTE,
1427 dcp::String::compose("libdcp %1", dcp::version),
1428 dcp::String::compose("libdcp %1", dcp::version),
1429 dcp::LocalTime().as_string(),
1433 check_verify_result (
1436 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1437 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1442 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1444 path dir = "build/test/verify_invalid_subtitle_start_time";
1445 prepare_directory (dir);
1446 auto dcp = make_simple (dir, 1, 106);
1449 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1450 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1451 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1452 "<ContentTitleText>Content</ContentTitleText>"
1453 "<AnnotationText>Annotation</AnnotationText>"
1454 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1455 "<ReelNumber>1</ReelNumber>"
1456 "<Language>de-DE</Language>"
1457 "<EditRate>24 1</EditRate>"
1458 "<TimeCodeRate>24</TimeCodeRate>"
1459 "<StartTime>00:00:02:00</StartTime>"
1460 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1462 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1463 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1464 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1470 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1471 BOOST_REQUIRE (xml_file);
1472 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1474 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1475 subs->write (dir / "subs.mxf");
1477 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1478 dcp->cpls().front()->reels().front()->add(reel_subs);
1480 dcp::Standard::SMPTE,
1481 dcp::String::compose("libdcp %1", dcp::version),
1482 dcp::String::compose("libdcp %1", dcp::version),
1483 dcp::LocalTime().as_string(),
1487 check_verify_result (
1490 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1491 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1499 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1502 , v_position(v_position_)
1514 shared_ptr<dcp::CPL>
1515 dcp_with_text (path dir, vector<TestText> subs)
1517 prepare_directory (dir);
1518 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1519 asset->set_start_time (dcp::Time());
1520 for (auto i: subs) {
1521 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1523 asset->set_language (dcp::LanguageTag("de-DE"));
1524 asset->write (dir / "subs.mxf");
1526 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1527 return write_dcp_with_single_asset (dir, reel_asset);
1531 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1533 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1534 /* Just too early */
1535 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1536 check_verify_result (
1539 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1540 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1546 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1548 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1549 /* Just late enough */
1550 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1551 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1555 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1557 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1558 prepare_directory (dir);
1560 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1561 asset1->set_start_time (dcp::Time());
1562 /* Just late enough */
1563 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1564 asset1->set_language (dcp::LanguageTag("de-DE"));
1565 asset1->write (dir / "subs1.mxf");
1566 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1567 auto reel1 = make_shared<dcp::Reel>();
1568 reel1->add (reel_asset1);
1569 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1570 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1571 reel1->add (markers1);
1573 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1574 asset2->set_start_time (dcp::Time());
1575 /* This would be too early on first reel but should be OK on the second */
1576 add_test_subtitle (asset2, 3, 4 * 24);
1577 asset2->set_language (dcp::LanguageTag("de-DE"));
1578 asset2->write (dir / "subs2.mxf");
1579 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1580 auto reel2 = make_shared<dcp::Reel>();
1581 reel2->add (reel_asset2);
1582 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1583 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1584 reel2->add (markers2);
1586 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1589 auto dcp = make_shared<dcp::DCP>(dir);
1592 dcp::Standard::SMPTE,
1593 dcp::String::compose("libdcp %1", dcp::version),
1594 dcp::String::compose("libdcp %1", dcp::version),
1595 dcp::LocalTime().as_string(),
1600 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1604 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1606 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1607 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1611 { 5 * 24 + 1, 6 * 24 },
1613 check_verify_result (
1616 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1617 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1622 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1624 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1625 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1629 { 5 * 24 + 16, 8 * 24 },
1631 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1635 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1637 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1638 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1639 check_verify_result (
1642 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1643 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1648 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1650 auto const dir = path("build/test/verify_valid_subtitle_duration");
1651 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1652 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1656 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1658 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1659 prepare_directory (dir);
1660 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1661 asset->set_start_time (dcp::Time());
1662 add_test_subtitle (asset, 0, 4 * 24);
1663 asset->set_language (dcp::LanguageTag("de-DE"));
1664 asset->write (dir / "subs.mxf");
1666 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1667 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1668 check_verify_result (
1671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1672 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1673 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1674 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1680 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1682 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1683 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1686 { 96, 200, 0.0, "We" },
1687 { 96, 200, 0.1, "have" },
1688 { 96, 200, 0.2, "four" },
1689 { 96, 200, 0.3, "lines" }
1691 check_verify_result (
1694 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1695 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1700 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1702 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1703 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1706 { 96, 200, 0.0, "We" },
1707 { 96, 200, 0.1, "have" },
1708 { 96, 200, 0.2, "four" },
1710 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1714 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1716 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1717 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1720 { 96, 300, 0.0, "We" },
1721 { 96, 300, 0.1, "have" },
1722 { 150, 180, 0.2, "four" },
1723 { 150, 180, 0.3, "lines" }
1725 check_verify_result (
1728 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1734 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1736 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1737 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1740 { 96, 300, 0.0, "We" },
1741 { 96, 300, 0.1, "have" },
1742 { 150, 180, 0.2, "four" },
1743 { 190, 250, 0.3, "lines" }
1745 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1749 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1751 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1752 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1755 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1757 check_verify_result (
1760 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1761 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1766 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1768 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1769 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1772 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1774 check_verify_result (
1777 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1778 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1783 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1785 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1786 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1789 { 96, 200, 0.0, "We" },
1790 { 96, 200, 0.1, "have" },
1791 { 96, 200, 0.2, "four" },
1792 { 96, 200, 0.3, "lines" }
1794 check_verify_result (
1797 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1798 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1803 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1805 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1806 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1809 { 96, 200, 0.0, "We" },
1810 { 96, 200, 0.1, "have" },
1811 { 96, 200, 0.2, "four" },
1813 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1817 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1819 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1820 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1823 { 96, 300, 0.0, "We" },
1824 { 96, 300, 0.1, "have" },
1825 { 150, 180, 0.2, "four" },
1826 { 150, 180, 0.3, "lines" }
1828 check_verify_result (
1831 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1832 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1837 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1839 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1840 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1843 { 96, 300, 0.0, "We" },
1844 { 96, 300, 0.1, "have" },
1845 { 150, 180, 0.2, "four" },
1846 { 190, 250, 0.3, "lines" }
1848 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1852 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1854 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1855 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1858 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1860 check_verify_result (
1863 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1864 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1869 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1871 path const dir("build/test/verify_invalid_sound_frame_rate");
1872 prepare_directory (dir);
1874 auto picture = simple_picture (dir, "foo");
1875 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1876 auto reel = make_shared<dcp::Reel>();
1877 reel->add (reel_picture);
1878 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1879 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1880 reel->add (reel_sound);
1881 reel->add (simple_markers());
1882 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1884 auto dcp = make_shared<dcp::DCP>(dir);
1887 dcp::Standard::SMPTE,
1888 dcp::String::compose("libdcp %1", dcp::version),
1889 dcp::String::compose("libdcp %1", dcp::version),
1890 dcp::LocalTime().as_string(),
1894 check_verify_result (
1897 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1898 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1903 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1905 path const dir("build/test/verify_missing_cpl_annotation_text");
1906 auto dcp = make_simple (dir);
1908 dcp::Standard::SMPTE,
1909 dcp::String::compose("libdcp %1", dcp::version),
1910 dcp::String::compose("libdcp %1", dcp::version),
1911 dcp::LocalTime().as_string(),
1915 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1917 auto const cpl = dcp->cpls()[0];
1920 BOOST_REQUIRE (cpl->file());
1921 Editor e(cpl->file().get());
1922 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1925 check_verify_result (
1928 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1929 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1934 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1936 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1937 auto dcp = make_simple (dir);
1939 dcp::Standard::SMPTE,
1940 dcp::String::compose("libdcp %1", dcp::version),
1941 dcp::String::compose("libdcp %1", dcp::version),
1942 dcp::LocalTime().as_string(),
1946 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1947 auto const cpl = dcp->cpls()[0];
1950 BOOST_REQUIRE (cpl->file());
1951 Editor e(cpl->file().get());
1952 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1955 check_verify_result (
1958 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1959 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1964 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1966 path const dir("build/test/verify_mismatched_asset_duration");
1967 prepare_directory (dir);
1968 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1969 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1971 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1972 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1974 auto reel = make_shared<dcp::Reel>(
1975 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1976 make_shared<dcp::ReelSoundAsset>(ms, 0)
1979 reel->add (simple_markers());
1984 dcp::Standard::SMPTE,
1985 dcp::String::compose("libdcp %1", dcp::version),
1986 dcp::String::compose("libdcp %1", dcp::version),
1987 dcp::LocalTime().as_string(),
1991 check_verify_result (
1994 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1995 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
2002 shared_ptr<dcp::CPL>
2003 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
2005 prepare_directory (dir);
2006 auto dcp = make_shared<dcp::DCP>(dir);
2007 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2009 auto constexpr reel_length = 192;
2011 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2012 subs->set_language (dcp::LanguageTag("de-DE"));
2013 subs->set_start_time (dcp::Time());
2014 subs->add (simple_subtitle());
2015 subs->write (dir / "subs.mxf");
2016 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2018 auto reel1 = make_shared<dcp::Reel>(
2019 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2020 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2024 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2027 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2028 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2029 reel1->add (markers1);
2033 auto reel2 = make_shared<dcp::Reel>(
2034 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2035 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2039 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2042 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2043 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2044 reel2->add (markers2);
2050 dcp::Standard::SMPTE,
2051 dcp::String::compose("libdcp %1", dcp::version),
2052 dcp::String::compose("libdcp %1", dcp::version),
2053 dcp::LocalTime().as_string(),
2061 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2064 path dir ("build/test/missing_main_subtitle_from_some_reels");
2065 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2066 check_verify_result (
2069 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2070 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2076 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2077 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2078 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2082 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2083 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2084 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2090 shared_ptr<dcp::CPL>
2091 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2093 prepare_directory (dir);
2094 auto dcp = make_shared<dcp::DCP>(dir);
2095 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2097 auto constexpr reel_length = 192;
2099 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2100 subs->set_language (dcp::LanguageTag("de-DE"));
2101 subs->set_start_time (dcp::Time());
2102 subs->add (simple_subtitle());
2103 subs->write (dir / "subs.mxf");
2105 auto reel1 = make_shared<dcp::Reel>(
2106 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2107 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2110 for (int i = 0; i < caps_in_reel1; ++i) {
2111 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2114 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2115 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2116 reel1->add (markers1);
2120 auto reel2 = make_shared<dcp::Reel>(
2121 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2122 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2125 for (int i = 0; i < caps_in_reel2; ++i) {
2126 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2129 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2130 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2131 reel2->add (markers2);
2137 dcp::Standard::SMPTE,
2138 dcp::String::compose("libdcp %1", dcp::version),
2139 dcp::String::compose("libdcp %1", dcp::version),
2140 dcp::LocalTime().as_string(),
2148 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2151 path dir ("build/test/mismatched_closed_caption_asset_counts");
2152 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2153 check_verify_result (
2156 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2157 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2162 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2163 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2164 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2168 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2169 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2170 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2177 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2179 prepare_directory (dir);
2180 auto dcp = make_shared<dcp::DCP>(dir);
2181 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2183 auto constexpr reel_length = 192;
2185 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2186 subs->set_language (dcp::LanguageTag("de-DE"));
2187 subs->set_start_time (dcp::Time());
2188 subs->add (simple_subtitle());
2189 subs->write (dir / "subs.mxf");
2190 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2193 auto reel = make_shared<dcp::Reel>(
2194 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2195 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2198 reel->add (reel_text);
2200 reel->add (simple_markers(reel_length));
2206 dcp::Standard::SMPTE,
2207 dcp::String::compose("libdcp %1", dcp::version),
2208 dcp::String::compose("libdcp %1", dcp::version),
2209 dcp::LocalTime().as_string(),
2213 check_verify_result (
2216 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2217 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2222 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2224 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2225 "build/test/verify_subtitle_entry_point_must_be_present",
2226 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2227 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2228 asset->unset_entry_point ();
2232 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2233 "build/test/verify_subtitle_entry_point_must_be_zero",
2234 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2235 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2236 asset->set_entry_point (4);
2240 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2241 "build/test/verify_closed_caption_entry_point_must_be_present",
2242 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2243 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2244 asset->unset_entry_point ();
2248 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2249 "build/test/verify_closed_caption_entry_point_must_be_zero",
2250 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2251 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2252 asset->set_entry_point (9);
2258 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2262 path const dir("build/test/verify_missing_hash");
2263 auto dcp = make_simple (dir);
2265 dcp::Standard::SMPTE,
2266 dcp::String::compose("libdcp %1", dcp::version),
2267 dcp::String::compose("libdcp %1", dcp::version),
2268 dcp::LocalTime().as_string(),
2272 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2273 auto const cpl = dcp->cpls()[0];
2276 BOOST_REQUIRE (cpl->file());
2277 Editor e(cpl->file().get());
2278 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2281 check_verify_result (
2284 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2285 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2292 verify_markers_test (
2294 vector<pair<dcp::Marker, dcp::Time>> markers,
2295 vector<dcp::VerificationNote> test_notes
2298 auto dcp = make_simple (dir);
2299 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2300 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2301 for (auto const& i: markers) {
2302 markers_asset->set (i.first, i.second);
2304 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2306 dcp::Standard::SMPTE,
2307 dcp::String::compose("libdcp %1", dcp::version),
2308 dcp::String::compose("libdcp %1", dcp::version),
2309 dcp::LocalTime().as_string(),
2313 check_verify_result ({dir}, test_notes);
2317 BOOST_AUTO_TEST_CASE (verify_markers)
2319 verify_markers_test (
2320 "build/test/verify_markers_all_correct",
2322 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2323 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2324 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2325 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2330 verify_markers_test (
2331 "build/test/verify_markers_missing_ffec",
2333 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2334 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2335 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2338 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2341 verify_markers_test (
2342 "build/test/verify_markers_missing_ffmc",
2344 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2345 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2346 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2349 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2352 verify_markers_test (
2353 "build/test/verify_markers_missing_ffoc",
2355 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2356 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2357 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2360 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2363 verify_markers_test (
2364 "build/test/verify_markers_missing_lfoc",
2366 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2367 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2368 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2371 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2374 verify_markers_test (
2375 "build/test/verify_markers_incorrect_ffoc",
2377 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2378 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2379 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2380 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2383 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2386 verify_markers_test (
2387 "build/test/verify_markers_incorrect_lfoc",
2389 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2390 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2391 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2392 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2395 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2400 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2402 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2403 prepare_directory (dir);
2404 auto dcp = make_simple (dir);
2405 auto cpl = dcp->cpls()[0];
2406 cpl->unset_version_number();
2408 dcp::Standard::SMPTE,
2409 dcp::String::compose("libdcp %1", dcp::version),
2410 dcp::String::compose("libdcp %1", dcp::version),
2411 dcp::LocalTime().as_string(),
2415 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2419 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2421 path dir = "build/test/verify_missing_extension_metadata1";
2422 auto dcp = make_simple (dir);
2424 dcp::Standard::SMPTE,
2425 dcp::String::compose("libdcp %1", dcp::version),
2426 dcp::String::compose("libdcp %1", dcp::version),
2427 dcp::LocalTime().as_string(),
2431 auto cpl = dcp->cpls()[0];
2434 Editor e (cpl->file().get());
2435 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2438 check_verify_result (
2441 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2442 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2447 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2449 path dir = "build/test/verify_missing_extension_metadata2";
2450 auto dcp = make_simple (dir);
2452 dcp::Standard::SMPTE,
2453 dcp::String::compose("libdcp %1", dcp::version),
2454 dcp::String::compose("libdcp %1", dcp::version),
2455 dcp::LocalTime().as_string(),
2459 auto cpl = dcp->cpls()[0];
2462 Editor e (cpl->file().get());
2463 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2466 check_verify_result (
2469 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2470 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2475 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2477 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2478 auto dcp = make_simple (dir);
2480 dcp::Standard::SMPTE,
2481 dcp::String::compose("libdcp %1", dcp::version),
2482 dcp::String::compose("libdcp %1", dcp::version),
2483 dcp::LocalTime().as_string(),
2487 auto const cpl = dcp->cpls()[0];
2490 Editor e (cpl->file().get());
2491 e.replace ("<meta:Name>A", "<meta:NameX>A");
2492 e.replace ("n</meta:Name>", "n</meta:NameX>");
2495 check_verify_result (
2498 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2499 { 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 },
2500 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2505 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2507 path dir = "build/test/verify_invalid_extension_metadata1";
2508 auto dcp = make_simple (dir);
2510 dcp::Standard::SMPTE,
2511 dcp::String::compose("libdcp %1", dcp::version),
2512 dcp::String::compose("libdcp %1", dcp::version),
2513 dcp::LocalTime().as_string(),
2517 auto cpl = dcp->cpls()[0];
2520 Editor e (cpl->file().get());
2521 e.replace ("Application", "Fred");
2524 check_verify_result (
2527 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2528 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2533 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2535 path dir = "build/test/verify_invalid_extension_metadata2";
2536 auto dcp = make_simple (dir);
2538 dcp::Standard::SMPTE,
2539 dcp::String::compose("libdcp %1", dcp::version),
2540 dcp::String::compose("libdcp %1", dcp::version),
2541 dcp::LocalTime().as_string(),
2545 auto cpl = dcp->cpls()[0];
2548 Editor e (cpl->file().get());
2549 e.replace ("DCP Constraints Profile", "Fred");
2552 check_verify_result (
2555 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2556 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2561 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2563 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2564 auto dcp = make_simple (dir);
2566 dcp::Standard::SMPTE,
2567 dcp::String::compose("libdcp %1", dcp::version),
2568 dcp::String::compose("libdcp %1", dcp::version),
2569 dcp::LocalTime().as_string(),
2573 auto const cpl = dcp->cpls()[0];
2576 Editor e (cpl->file().get());
2577 e.replace ("<meta:Value>", "<meta:ValueX>");
2578 e.replace ("</meta:Value>", "</meta:ValueX>");
2581 check_verify_result (
2584 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2585 { 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 },
2586 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2591 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2593 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2594 auto dcp = make_simple (dir);
2596 dcp::Standard::SMPTE,
2597 dcp::String::compose("libdcp %1", dcp::version),
2598 dcp::String::compose("libdcp %1", dcp::version),
2599 dcp::LocalTime().as_string(),
2603 auto const cpl = dcp->cpls()[0];
2606 Editor e (cpl->file().get());
2607 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2610 check_verify_result (
2613 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2614 { 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() },
2619 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2621 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2622 auto dcp = make_simple (dir);
2624 dcp::Standard::SMPTE,
2625 dcp::String::compose("libdcp %1", dcp::version),
2626 dcp::String::compose("libdcp %1", dcp::version),
2627 dcp::LocalTime().as_string(),
2631 auto const cpl = dcp->cpls()[0];
2634 Editor e (cpl->file().get());
2635 e.replace ("<meta:Property>", "<meta:PropertyX>");
2636 e.replace ("</meta:Property>", "</meta:PropertyX>");
2639 check_verify_result (
2642 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2643 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2644 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2649 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2651 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2652 auto dcp = make_simple (dir);
2654 dcp::Standard::SMPTE,
2655 dcp::String::compose("libdcp %1", dcp::version),
2656 dcp::String::compose("libdcp %1", dcp::version),
2657 dcp::LocalTime().as_string(),
2661 auto const cpl = dcp->cpls()[0];
2664 Editor e (cpl->file().get());
2665 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2666 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2669 check_verify_result (
2672 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2673 { 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 },
2674 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2680 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2682 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2683 prepare_directory (dir);
2684 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2685 copy_file (i.path(), dir / i.path().filename());
2688 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2689 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2693 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2696 check_verify_result (
2699 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2701 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2702 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2703 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2704 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2706 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2711 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2713 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2714 prepare_directory (dir);
2715 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2716 copy_file (i.path(), dir / i.path().filename());
2719 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2720 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2723 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2726 check_verify_result (
2729 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2730 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2731 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2732 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2733 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2734 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2735 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2740 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2742 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2743 prepare_directory (dir);
2744 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2745 copy_file (i.path(), dir / i.path().filename());
2749 Editor e (dir / dcp_test1_pkl);
2750 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2753 check_verify_result ({dir}, {});
2757 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2759 path dir ("build/test/verify_must_not_be_partially_encrypted");
2760 prepare_directory (dir);
2764 auto signer = make_shared<dcp::CertificateChain>();
2765 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2766 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2767 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2768 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2770 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2774 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2777 auto writer = mp->start_write (dir / "video.mxf", false);
2778 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2779 for (int i = 0; i < 24; ++i) {
2780 writer->write (j2c.data(), j2c.size());
2782 writer->finalize ();
2784 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2786 auto reel = make_shared<dcp::Reel>(
2787 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2788 make_shared<dcp::ReelSoundAsset>(ms, 0)
2791 reel->add (simple_markers());
2795 cpl->set_content_version (
2796 {"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"}
2798 cpl->set_annotation_text ("A Test DCP");
2799 cpl->set_issuer ("OpenDCP 0.0.25");
2800 cpl->set_creator ("OpenDCP 0.0.25");
2801 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2802 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2803 cpl->set_main_sound_sample_rate (48000);
2804 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2805 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2806 cpl->set_version_number (1);
2810 d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2812 check_verify_result (
2815 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2820 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2822 vector<dcp::VerificationNote> notes;
2823 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"));
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 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2833 vector<dcp::VerificationNote> notes;
2834 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2835 auto reader = picture.start_read ();
2836 auto frame = reader->get_frame (0);
2837 verify_j2k (frame, notes);
2838 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2842 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2844 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2845 prepare_directory (dir);
2846 auto dcp = make_simple (dir);
2847 dcp->write_xml (dcp::Standard::SMPTE);
2848 vector<dcp::VerificationNote> notes;
2849 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2850 auto reader = picture.start_read ();
2851 auto frame = reader->get_frame (0);
2852 verify_j2k (frame, notes);
2853 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2857 /** Check that ResourceID and the XML ID being different is spotted */
2858 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2860 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2861 prepare_directory (dir);
2863 ASDCP::WriterInfo writer_info;
2864 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2867 auto mxf_id = dcp::make_uuid ();
2868 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2869 BOOST_REQUIRE (c == Kumu::UUID_Length);
2871 auto resource_id = dcp::make_uuid ();
2872 ASDCP::TimedText::TimedTextDescriptor descriptor;
2873 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2874 DCP_ASSERT (c == Kumu::UUID_Length);
2876 auto xml_id = dcp::make_uuid ();
2877 ASDCP::TimedText::MXFWriter writer;
2878 auto subs_mxf = dir / "subs.mxf";
2879 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2880 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2881 writer.WriteTimedTextResource (dcp::String::compose(
2882 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2883 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2884 "<Id>urn:uuid:%1</Id>"
2885 "<ContentTitleText>Content</ContentTitleText>"
2886 "<AnnotationText>Annotation</AnnotationText>"
2887 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2888 "<ReelNumber>1</ReelNumber>"
2889 "<Language>en-US</Language>"
2890 "<EditRate>25 1</EditRate>"
2891 "<TimeCodeRate>25</TimeCodeRate>"
2892 "<StartTime>00:00:00:00</StartTime>"
2894 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2895 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2896 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2905 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2906 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2908 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2910 check_verify_result (
2913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2915 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2916 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2921 /** Check that ResourceID and the MXF ID being the same is spotted */
2922 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2924 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2925 prepare_directory (dir);
2927 ASDCP::WriterInfo writer_info;
2928 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2931 auto mxf_id = dcp::make_uuid ();
2932 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2933 BOOST_REQUIRE (c == Kumu::UUID_Length);
2935 auto resource_id = mxf_id;
2936 ASDCP::TimedText::TimedTextDescriptor descriptor;
2937 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2938 DCP_ASSERT (c == Kumu::UUID_Length);
2940 auto xml_id = resource_id;
2941 ASDCP::TimedText::MXFWriter writer;
2942 auto subs_mxf = dir / "subs.mxf";
2943 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2944 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2945 writer.WriteTimedTextResource (dcp::String::compose(
2946 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2947 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2948 "<Id>urn:uuid:%1</Id>"
2949 "<ContentTitleText>Content</ContentTitleText>"
2950 "<AnnotationText>Annotation</AnnotationText>"
2951 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2952 "<ReelNumber>1</ReelNumber>"
2953 "<Language>en-US</Language>"
2954 "<EditRate>25 1</EditRate>"
2955 "<TimeCodeRate>25</TimeCodeRate>"
2956 "<StartTime>00:00:00:00</StartTime>"
2958 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2959 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2960 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2969 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2970 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2972 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2974 check_verify_result (
2977 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2978 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2979 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }