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>
69 using std::make_shared;
70 using boost::optional;
71 using namespace boost::filesystem;
72 using std::shared_ptr;
75 static list<pair<string, optional<path>>> stages;
76 static string const dcp_test1_pkl_id = "6af1e0c1-c441-47f8-a502-3efd46b1fa4f";
77 static string const dcp_test1_pkl = "pkl_" + dcp_test1_pkl_id + ".xml";
78 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
79 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
80 static string const dcp_test1_asset_map_id = "5d51e8a1-b2a5-4da6-9b66-4615c3609440";
81 static string const encryption_test_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
82 static string const encryption_test_pkl_id = "627ad740-ae36-4c49-92bb-553a9f09c4f8";
85 stage (string s, optional<path> p)
87 stages.push_back (make_pair (s, p));
97 prepare_directory (path path)
99 using namespace boost::filesystem;
101 create_directories (path);
106 setup (int reference_number, string verify_test_suffix)
108 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
109 prepare_directory (dir);
110 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
111 copy_file (i.path(), dir / i.path().filename());
120 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
122 auto reel = make_shared<dcp::Reel>();
123 reel->add (reel_asset);
124 reel->add (simple_markers());
126 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, standard);
128 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, dcp::Standard::SMPTE);
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::String::compose("libdcp %1", dcp::version),
785 dcp::String::compose("libdcp %1", dcp::version),
786 dcp::LocalTime().as_string(),
792 path find_cpl (path dir)
794 for (auto i: directory_iterator(dir)) {
795 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
800 BOOST_REQUIRE (false);
805 /* DCP with invalid CompositionMetadataAsset */
806 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
808 using namespace boost::filesystem;
810 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
811 prepare_directory (dir);
813 auto reel = make_shared<dcp::Reel>();
814 reel->add (black_picture_asset(dir));
815 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
817 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
818 cpl->set_main_sound_sample_rate (48000);
819 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
820 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
821 cpl->set_version_number (1);
823 reel->add (simple_markers());
828 dcp::String::compose("libdcp %1", dcp::version),
829 dcp::String::compose("libdcp %1", dcp::version),
830 dcp::LocalTime().as_string(),
835 Editor e (find_cpl(dir));
836 e.replace ("MainSound", "MainSoundX");
839 check_verify_result (
842 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
845 dcp::VerificationNote::Type::ERROR,
846 dcp::VerificationNote::Code::INVALID_XML,
847 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
848 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
849 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
850 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
851 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
852 "ExtensionMetadataList?,)'"),
853 canonical(cpl->file().get()),
856 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
861 /* DCP with invalid CompositionMetadataAsset */
862 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
864 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
865 prepare_directory (dir);
867 auto reel = make_shared<dcp::Reel>();
868 reel->add (black_picture_asset(dir));
869 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
871 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
872 cpl->set_main_sound_sample_rate (48000);
873 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
874 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
879 dcp::String::compose("libdcp %1", dcp::version),
880 dcp::String::compose("libdcp %1", dcp::version),
881 dcp::LocalTime().as_string(),
886 Editor e (find_cpl(dir));
887 e.replace ("meta:Width", "meta:WidthX");
890 check_verify_result (
892 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
897 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
899 path const dir("build/test/verify_invalid_language1");
900 prepare_directory (dir);
901 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
902 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
903 asset->_language = "wrong-andbad";
904 asset->write (dir / "subs.mxf");
905 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
906 reel_asset->_language = "badlang";
907 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
909 check_verify_result (
912 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
919 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
920 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
922 path const dir("build/test/verify_invalid_language2");
923 prepare_directory (dir);
924 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
925 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
926 asset->_language = "wrong-andbad";
927 asset->write (dir / "subs.mxf");
928 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 6046, 0);
929 reel_asset->_language = "badlang";
930 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
932 check_verify_result (
935 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
936 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
937 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
942 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
943 * the release territory.
945 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
947 path const dir("build/test/verify_invalid_language3");
948 prepare_directory (dir);
950 auto picture = simple_picture (dir, "foo");
951 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
952 auto reel = make_shared<dcp::Reel>();
953 reel->add (reel_picture);
954 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
955 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
956 reel->add (reel_sound);
957 reel->add (simple_markers());
959 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
961 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
962 cpl->_additional_subtitle_languages.push_back("andso-is-this");
963 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
964 cpl->set_main_sound_sample_rate (48000);
965 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
966 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
967 cpl->set_version_number (1);
968 cpl->_release_territory = "fred-jim";
969 auto dcp = make_shared<dcp::DCP>(dir);
972 dcp::String::compose("libdcp %1", dcp::version),
973 dcp::String::compose("libdcp %1", dcp::version),
974 dcp::LocalTime().as_string(),
978 check_verify_result (
981 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
982 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
983 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
984 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
990 vector<dcp::VerificationNote>
991 check_picture_size (int width, int height, int frame_rate, bool three_d)
993 using namespace boost::filesystem;
995 path dcp_path = "build/test/verify_picture_test";
996 prepare_directory (dcp_path);
998 shared_ptr<dcp::PictureAsset> mp;
1000 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1002 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
1004 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1006 auto image = black_image (dcp::Size(width, height));
1007 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1008 int const length = three_d ? frame_rate * 2 : frame_rate;
1009 for (int i = 0; i < length; ++i) {
1010 picture_writer->write (j2c.data(), j2c.size());
1012 picture_writer->finalize ();
1014 auto d = make_shared<dcp::DCP>(dcp_path);
1015 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1016 cpl->set_annotation_text ("A Test DCP");
1017 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1018 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1019 cpl->set_main_sound_sample_rate (48000);
1020 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1021 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1022 cpl->set_version_number (1);
1024 auto reel = make_shared<dcp::Reel>();
1027 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1029 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1032 reel->add (simple_markers(frame_rate));
1038 dcp::String::compose("libdcp %1", dcp::version),
1039 dcp::String::compose("libdcp %1", dcp::version),
1040 dcp::LocalTime().as_string(),
1044 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1050 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1052 auto notes = check_picture_size(width, height, frame_rate, three_d);
1053 BOOST_CHECK_EQUAL (notes.size(), 0U);
1059 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1061 auto notes = check_picture_size(width, height, frame_rate, three_d);
1062 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1063 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1064 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1070 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1072 auto notes = check_picture_size(width, height, frame_rate, three_d);
1073 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1074 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1075 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1081 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1083 auto notes = check_picture_size(width, height, frame_rate, three_d);
1084 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1085 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1086 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1090 BOOST_AUTO_TEST_CASE (verify_picture_size)
1092 using namespace boost::filesystem;
1095 check_picture_size_ok (2048, 858, 24, false);
1096 check_picture_size_ok (2048, 858, 25, false);
1097 check_picture_size_ok (2048, 858, 48, false);
1098 check_picture_size_ok (2048, 858, 24, true);
1099 check_picture_size_ok (2048, 858, 25, true);
1100 check_picture_size_ok (2048, 858, 48, true);
1103 check_picture_size_ok (1998, 1080, 24, false);
1104 check_picture_size_ok (1998, 1080, 25, false);
1105 check_picture_size_ok (1998, 1080, 48, false);
1106 check_picture_size_ok (1998, 1080, 24, true);
1107 check_picture_size_ok (1998, 1080, 25, true);
1108 check_picture_size_ok (1998, 1080, 48, true);
1111 check_picture_size_ok (4096, 1716, 24, false);
1114 check_picture_size_ok (3996, 2160, 24, false);
1116 /* Bad frame size */
1117 check_picture_size_bad_frame_size (2050, 858, 24, false);
1118 check_picture_size_bad_frame_size (2048, 658, 25, false);
1119 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1120 check_picture_size_bad_frame_size (4000, 2000, 24, true);
1122 /* Bad 2K frame rate */
1123 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1124 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1125 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1127 /* Bad 4K frame rate */
1128 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1129 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1132 auto notes = check_picture_size(3996, 2160, 24, true);
1133 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1134 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1135 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1141 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1144 make_shared<dcp::SubtitleString>(
1152 dcp::Time(start_frame, 24, 24),
1153 dcp::Time(end_frame, 24, 24),
1155 dcp::HAlign::CENTER,
1157 dcp::VAlign::CENTER,
1158 dcp::Direction::LTR,
1169 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1171 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1172 prepare_directory (dir);
1174 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1175 for (int i = 0; i < 2048; ++i) {
1176 add_test_subtitle (asset, i * 24, i * 24 + 20);
1178 asset->set_language (dcp::LanguageTag("de-DE"));
1179 asset->write (dir / "subs.mxf");
1180 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 49148, 0);
1181 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1183 check_verify_result (
1186 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1188 dcp::VerificationNote::Type::BV21_ERROR,
1189 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1191 canonical(dir / "subs.mxf")
1193 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1194 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1200 shared_ptr<dcp::SMPTESubtitleAsset>
1201 make_large_subtitle_asset (path font_file)
1203 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1204 dcp::ArrayData big_fake_font(1024 * 1024);
1205 big_fake_font.write (font_file);
1206 for (int i = 0; i < 116; ++i) {
1207 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1215 verify_timed_text_asset_too_large (string name)
1217 auto const dir = path("build/test") / name;
1218 prepare_directory (dir);
1219 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1220 add_test_subtitle (asset, 0, 240);
1221 asset->set_language (dcp::LanguageTag("de-DE"));
1222 asset->write (dir / "subs.mxf");
1224 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 240, 0);
1225 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1227 check_verify_result (
1230 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1231 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1232 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1233 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1234 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1239 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1241 verify_timed_text_asset_too_large<dcp::ReelSMPTESubtitleAsset>("verify_subtitle_asset_too_large");
1242 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1246 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1248 path dir = "build/test/verify_missing_subtitle_language";
1249 prepare_directory (dir);
1250 auto dcp = make_simple (dir, 1, 106);
1253 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1254 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1255 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1256 "<ContentTitleText>Content</ContentTitleText>"
1257 "<AnnotationText>Annotation</AnnotationText>"
1258 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1259 "<ReelNumber>1</ReelNumber>"
1260 "<EditRate>24 1</EditRate>"
1261 "<TimeCodeRate>24</TimeCodeRate>"
1262 "<StartTime>00:00:00:00</StartTime>"
1263 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1265 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1266 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1267 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1273 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1274 BOOST_REQUIRE (xml_file);
1275 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1277 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1278 subs->write (dir / "subs.mxf");
1280 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1281 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1283 dcp::String::compose("libdcp %1", dcp::version),
1284 dcp::String::compose("libdcp %1", dcp::version),
1285 dcp::LocalTime().as_string(),
1289 check_verify_result (
1292 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1293 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1298 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1300 path path ("build/test/verify_mismatched_subtitle_languages");
1301 auto constexpr reel_length = 192;
1302 auto dcp = make_simple (path, 2, reel_length);
1303 auto cpl = dcp->cpls()[0];
1306 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1307 subs->set_language (dcp::LanguageTag("de-DE"));
1308 subs->add (simple_subtitle());
1309 subs->write (path / "subs1.mxf");
1310 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1311 cpl->reels()[0]->add(reel_subs);
1315 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1316 subs->set_language (dcp::LanguageTag("en-US"));
1317 subs->add (simple_subtitle());
1318 subs->write (path / "subs2.mxf");
1319 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
1320 cpl->reels()[1]->add(reel_subs);
1324 dcp::String::compose("libdcp %1", dcp::version),
1325 dcp::String::compose("libdcp %1", dcp::version),
1326 dcp::LocalTime().as_string(),
1330 check_verify_result (
1333 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1334 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1335 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1340 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1342 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1343 auto constexpr reel_length = 192;
1344 auto dcp = make_simple (path, 2, reel_length);
1345 auto cpl = dcp->cpls()[0];
1348 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1349 ccaps->set_language (dcp::LanguageTag("de-DE"));
1350 ccaps->add (simple_subtitle());
1351 ccaps->write (path / "subs1.mxf");
1352 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1353 cpl->reels()[0]->add(reel_ccaps);
1357 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1358 ccaps->set_language (dcp::LanguageTag("en-US"));
1359 ccaps->add (simple_subtitle());
1360 ccaps->write (path / "subs2.mxf");
1361 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), reel_length, 0);
1362 cpl->reels()[1]->add(reel_ccaps);
1366 dcp::String::compose("libdcp %1", dcp::version),
1367 dcp::String::compose("libdcp %1", dcp::version),
1368 dcp::LocalTime().as_string(),
1372 check_verify_result (
1375 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1376 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1381 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1383 path dir = "build/test/verify_missing_subtitle_start_time";
1384 prepare_directory (dir);
1385 auto dcp = make_simple (dir, 1, 106);
1388 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1389 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1390 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1391 "<ContentTitleText>Content</ContentTitleText>"
1392 "<AnnotationText>Annotation</AnnotationText>"
1393 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1394 "<ReelNumber>1</ReelNumber>"
1395 "<Language>de-DE</Language>"
1396 "<EditRate>24 1</EditRate>"
1397 "<TimeCodeRate>24</TimeCodeRate>"
1398 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1400 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1401 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1402 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1408 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1409 BOOST_REQUIRE (xml_file);
1410 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1412 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1413 subs->write (dir / "subs.mxf");
1415 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1416 dcp->cpls()[0]->reels()[0]->add(reel_subs);
1418 dcp::String::compose("libdcp %1", dcp::version),
1419 dcp::String::compose("libdcp %1", dcp::version),
1420 dcp::LocalTime().as_string(),
1424 check_verify_result (
1427 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1428 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1433 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1435 path dir = "build/test/verify_invalid_subtitle_start_time";
1436 prepare_directory (dir);
1437 auto dcp = make_simple (dir, 1, 106);
1440 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1441 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1442 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1443 "<ContentTitleText>Content</ContentTitleText>"
1444 "<AnnotationText>Annotation</AnnotationText>"
1445 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1446 "<ReelNumber>1</ReelNumber>"
1447 "<Language>de-DE</Language>"
1448 "<EditRate>24 1</EditRate>"
1449 "<TimeCodeRate>24</TimeCodeRate>"
1450 "<StartTime>00:00:02:00</StartTime>"
1451 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1453 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1454 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1455 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1461 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1462 BOOST_REQUIRE (xml_file);
1463 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1465 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1466 subs->write (dir / "subs.mxf");
1468 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 106, 0);
1469 dcp->cpls().front()->reels().front()->add(reel_subs);
1471 dcp::String::compose("libdcp %1", dcp::version),
1472 dcp::String::compose("libdcp %1", dcp::version),
1473 dcp::LocalTime().as_string(),
1477 check_verify_result (
1480 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1481 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1489 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1492 , v_position(v_position_)
1504 shared_ptr<dcp::CPL>
1505 dcp_with_text (path dir, vector<TestText> subs)
1507 prepare_directory (dir);
1508 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1509 asset->set_start_time (dcp::Time());
1510 for (auto i: subs) {
1511 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1513 asset->set_language (dcp::LanguageTag("de-DE"));
1514 asset->write (dir / "subs.mxf");
1516 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), asset->intrinsic_duration(), 0);
1517 return write_dcp_with_single_asset (dir, reel_asset);
1521 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1523 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1524 /* Just too early */
1525 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1526 check_verify_result (
1529 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1530 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1536 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1538 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1539 /* Just late enough */
1540 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1541 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1545 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1547 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1548 prepare_directory (dir);
1550 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1551 asset1->set_start_time (dcp::Time());
1552 /* Just late enough */
1553 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1554 asset1->set_language (dcp::LanguageTag("de-DE"));
1555 asset1->write (dir / "subs1.mxf");
1556 auto reel_asset1 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset1, dcp::Fraction(24, 1), 5 * 24, 0);
1557 auto reel1 = make_shared<dcp::Reel>();
1558 reel1->add (reel_asset1);
1559 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 5 * 24, 0);
1560 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1561 reel1->add (markers1);
1563 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1564 asset2->set_start_time (dcp::Time());
1565 /* This would be too early on first reel but should be OK on the second */
1566 add_test_subtitle (asset2, 3, 4 * 24);
1567 asset2->set_language (dcp::LanguageTag("de-DE"));
1568 asset2->write (dir / "subs2.mxf");
1569 auto reel_asset2 = make_shared<dcp::ReelSMPTESubtitleAsset>(asset2, dcp::Fraction(24, 1), 4 * 24, 0);
1570 auto reel2 = make_shared<dcp::Reel>();
1571 reel2->add (reel_asset2);
1572 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 4 * 24, 0);
1573 markers2->set (dcp::Marker::LFOC, dcp::Time(4 * 24 - 1, 24, 24));
1574 reel2->add (markers2);
1576 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1579 auto dcp = make_shared<dcp::DCP>(dir);
1582 dcp::String::compose("libdcp %1", dcp::version),
1583 dcp::String::compose("libdcp %1", dcp::version),
1584 dcp::LocalTime().as_string(),
1589 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1593 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1595 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1596 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1600 { 5 * 24 + 1, 6 * 24 },
1602 check_verify_result (
1605 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1606 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1611 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1613 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1614 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1618 { 5 * 24 + 16, 8 * 24 },
1620 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1624 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1626 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1627 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1628 check_verify_result (
1631 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1632 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1637 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1639 auto const dir = path("build/test/verify_valid_subtitle_duration");
1640 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1641 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1645 BOOST_AUTO_TEST_CASE (verify_subtitle_overlapping_reel_boundary)
1647 auto const dir = path("build/test/verify_subtitle_overlapping_reel_boundary");
1648 prepare_directory (dir);
1649 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1650 asset->set_start_time (dcp::Time());
1651 add_test_subtitle (asset, 0, 4 * 24);
1652 asset->set_language (dcp::LanguageTag("de-DE"));
1653 asset->write (dir / "subs.mxf");
1655 auto reel_asset = make_shared<dcp::ReelSMPTESubtitleAsset>(asset, dcp::Fraction(24, 1), 3 * 24, 0);
1656 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1657 check_verify_result (
1660 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "72 96", boost::filesystem::canonical(asset->file().get()) },
1661 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1662 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::SUBTITLE_OVERLAPS_REEL_BOUNDARY },
1663 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1669 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1671 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1672 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1675 { 96, 200, 0.0, "We" },
1676 { 96, 200, 0.1, "have" },
1677 { 96, 200, 0.2, "four" },
1678 { 96, 200, 0.3, "lines" }
1680 check_verify_result (
1683 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1684 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1689 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1691 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1692 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1695 { 96, 200, 0.0, "We" },
1696 { 96, 200, 0.1, "have" },
1697 { 96, 200, 0.2, "four" },
1699 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1703 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1705 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1706 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1709 { 96, 300, 0.0, "We" },
1710 { 96, 300, 0.1, "have" },
1711 { 150, 180, 0.2, "four" },
1712 { 150, 180, 0.3, "lines" }
1714 check_verify_result (
1717 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1718 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1723 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1725 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1726 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1729 { 96, 300, 0.0, "We" },
1730 { 96, 300, 0.1, "have" },
1731 { 150, 180, 0.2, "four" },
1732 { 190, 250, 0.3, "lines" }
1734 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1738 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1740 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1741 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1744 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1746 check_verify_result (
1749 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1755 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1757 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1758 auto cpl = dcp_with_text<dcp::ReelSMPTESubtitleAsset> (
1761 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1763 check_verify_result (
1766 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1767 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1772 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1774 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1775 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1778 { 96, 200, 0.0, "We" },
1779 { 96, 200, 0.1, "have" },
1780 { 96, 200, 0.2, "four" },
1781 { 96, 200, 0.3, "lines" }
1783 check_verify_result (
1786 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1787 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1792 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1794 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1795 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1798 { 96, 200, 0.0, "We" },
1799 { 96, 200, 0.1, "have" },
1800 { 96, 200, 0.2, "four" },
1802 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1806 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1808 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1809 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1812 { 96, 300, 0.0, "We" },
1813 { 96, 300, 0.1, "have" },
1814 { 150, 180, 0.2, "four" },
1815 { 150, 180, 0.3, "lines" }
1817 check_verify_result (
1820 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1821 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1826 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1828 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1829 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1832 { 96, 300, 0.0, "We" },
1833 { 96, 300, 0.1, "have" },
1834 { 150, 180, 0.2, "four" },
1835 { 190, 250, 0.3, "lines" }
1837 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1841 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1843 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1844 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1847 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1849 check_verify_result (
1852 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1853 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1858 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1860 path const dir("build/test/verify_invalid_sound_frame_rate");
1861 prepare_directory (dir);
1863 auto picture = simple_picture (dir, "foo");
1864 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1865 auto reel = make_shared<dcp::Reel>();
1866 reel->add (reel_picture);
1867 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1868 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1869 reel->add (reel_sound);
1870 reel->add (simple_markers());
1871 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1873 auto dcp = make_shared<dcp::DCP>(dir);
1876 dcp::String::compose("libdcp %1", dcp::version),
1877 dcp::String::compose("libdcp %1", dcp::version),
1878 dcp::LocalTime().as_string(),
1882 check_verify_result (
1885 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1891 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1893 path const dir("build/test/verify_missing_cpl_annotation_text");
1894 auto dcp = make_simple (dir);
1896 dcp::String::compose("libdcp %1", dcp::version),
1897 dcp::String::compose("libdcp %1", dcp::version),
1898 dcp::LocalTime().as_string(),
1902 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1904 auto const cpl = dcp->cpls()[0];
1907 BOOST_REQUIRE (cpl->file());
1908 Editor e(cpl->file().get());
1909 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1912 check_verify_result (
1915 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1916 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1921 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1923 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1924 auto dcp = make_simple (dir);
1926 dcp::String::compose("libdcp %1", dcp::version),
1927 dcp::String::compose("libdcp %1", dcp::version),
1928 dcp::LocalTime().as_string(),
1932 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1933 auto const cpl = dcp->cpls()[0];
1936 BOOST_REQUIRE (cpl->file());
1937 Editor e(cpl->file().get());
1938 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1941 check_verify_result (
1944 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1945 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1950 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1952 path const dir("build/test/verify_mismatched_asset_duration");
1953 prepare_directory (dir);
1954 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1955 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1957 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1958 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1960 auto reel = make_shared<dcp::Reel>(
1961 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1962 make_shared<dcp::ReelSoundAsset>(ms, 0)
1965 reel->add (simple_markers());
1970 dcp::String::compose("libdcp %1", dcp::version),
1971 dcp::String::compose("libdcp %1", dcp::version),
1972 dcp::LocalTime().as_string(),
1976 check_verify_result (
1979 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1980 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1987 shared_ptr<dcp::CPL>
1988 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1990 prepare_directory (dir);
1991 auto dcp = make_shared<dcp::DCP>(dir);
1992 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
1994 auto constexpr reel_length = 192;
1996 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1997 subs->set_language (dcp::LanguageTag("de-DE"));
1998 subs->set_start_time (dcp::Time());
1999 subs->add (simple_subtitle());
2000 subs->write (dir / "subs.mxf");
2001 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0);
2003 auto reel1 = make_shared<dcp::Reel>(
2004 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2005 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2009 reel1->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2012 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2013 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2014 reel1->add (markers1);
2018 auto reel2 = 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 reel2->add (make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2027 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2028 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2029 reel2->add (markers2);
2035 dcp::String::compose("libdcp %1", dcp::version),
2036 dcp::String::compose("libdcp %1", dcp::version),
2037 dcp::LocalTime().as_string(),
2045 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2048 path dir ("build/test/missing_main_subtitle_from_some_reels");
2049 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2050 check_verify_result (
2053 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2054 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2060 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2061 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2062 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2066 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2067 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2068 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2074 shared_ptr<dcp::CPL>
2075 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2077 prepare_directory (dir);
2078 auto dcp = make_shared<dcp::DCP>(dir);
2079 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2081 auto constexpr reel_length = 192;
2083 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2084 subs->set_language (dcp::LanguageTag("de-DE"));
2085 subs->set_start_time (dcp::Time());
2086 subs->add (simple_subtitle());
2087 subs->write (dir / "subs.mxf");
2089 auto reel1 = make_shared<dcp::Reel>(
2090 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2091 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2094 for (int i = 0; i < caps_in_reel1; ++i) {
2095 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2098 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2099 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2100 reel1->add (markers1);
2104 auto reel2 = make_shared<dcp::Reel>(
2105 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2106 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2109 for (int i = 0; i < caps_in_reel2; ++i) {
2110 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), reel_length, 0));
2113 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), reel_length, 0);
2114 markers2->set (dcp::Marker::LFOC, dcp::Time(reel_length - 1, 24, 24));
2115 reel2->add (markers2);
2121 dcp::String::compose("libdcp %1", dcp::version),
2122 dcp::String::compose("libdcp %1", dcp::version),
2123 dcp::LocalTime().as_string(),
2131 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2134 path dir ("build/test/mismatched_closed_caption_asset_counts");
2135 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2136 check_verify_result (
2139 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2140 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2145 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2146 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2147 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2151 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2152 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2153 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2160 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2162 prepare_directory (dir);
2163 auto dcp = make_shared<dcp::DCP>(dir);
2164 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2166 auto constexpr reel_length = 192;
2168 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2169 subs->set_language (dcp::LanguageTag("de-DE"));
2170 subs->set_start_time (dcp::Time());
2171 subs->add (simple_subtitle());
2172 subs->write (dir / "subs.mxf");
2173 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), reel_length, 0);
2176 auto reel = make_shared<dcp::Reel>(
2177 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", reel_length), 0),
2178 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", reel_length), 0)
2181 reel->add (reel_text);
2183 reel->add (simple_markers(reel_length));
2189 dcp::String::compose("libdcp %1", dcp::version),
2190 dcp::String::compose("libdcp %1", dcp::version),
2191 dcp::LocalTime().as_string(),
2195 check_verify_result (
2198 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2199 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2204 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2206 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2207 "build/test/verify_subtitle_entry_point_must_be_present",
2208 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2209 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2210 asset->unset_entry_point ();
2214 verify_text_entry_point_check<dcp::ReelSMPTESubtitleAsset> (
2215 "build/test/verify_subtitle_entry_point_must_be_zero",
2216 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2217 [](shared_ptr<dcp::ReelSMPTESubtitleAsset> asset) {
2218 asset->set_entry_point (4);
2222 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2223 "build/test/verify_closed_caption_entry_point_must_be_present",
2224 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2225 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2226 asset->unset_entry_point ();
2230 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2231 "build/test/verify_closed_caption_entry_point_must_be_zero",
2232 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2233 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2234 asset->set_entry_point (9);
2240 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2244 path const dir("build/test/verify_missing_hash");
2245 auto dcp = make_simple (dir);
2247 dcp::String::compose("libdcp %1", dcp::version),
2248 dcp::String::compose("libdcp %1", dcp::version),
2249 dcp::LocalTime().as_string(),
2253 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2254 auto const cpl = dcp->cpls()[0];
2257 BOOST_REQUIRE (cpl->file());
2258 Editor e(cpl->file().get());
2259 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2262 check_verify_result (
2265 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2266 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2273 verify_markers_test (
2275 vector<pair<dcp::Marker, dcp::Time>> markers,
2276 vector<dcp::VerificationNote> test_notes
2279 auto dcp = make_simple (dir);
2280 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2281 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2282 for (auto const& i: markers) {
2283 markers_asset->set (i.first, i.second);
2285 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2287 dcp::String::compose("libdcp %1", dcp::version),
2288 dcp::String::compose("libdcp %1", dcp::version),
2289 dcp::LocalTime().as_string(),
2293 check_verify_result ({dir}, test_notes);
2297 BOOST_AUTO_TEST_CASE (verify_markers)
2299 verify_markers_test (
2300 "build/test/verify_markers_all_correct",
2302 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2303 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2304 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2305 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2310 verify_markers_test (
2311 "build/test/verify_markers_missing_ffec",
2313 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2314 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2315 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2318 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2321 verify_markers_test (
2322 "build/test/verify_markers_missing_ffmc",
2324 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2325 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2326 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2329 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2332 verify_markers_test (
2333 "build/test/verify_markers_missing_ffoc",
2335 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2336 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2337 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2340 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2343 verify_markers_test (
2344 "build/test/verify_markers_missing_lfoc",
2346 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2347 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2348 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2351 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2354 verify_markers_test (
2355 "build/test/verify_markers_incorrect_ffoc",
2357 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2358 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2359 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2360 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2363 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2366 verify_markers_test (
2367 "build/test/verify_markers_incorrect_lfoc",
2369 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2370 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2371 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2372 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2375 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2380 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2382 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2383 prepare_directory (dir);
2384 auto dcp = make_simple (dir);
2385 auto cpl = dcp->cpls()[0];
2386 cpl->unset_version_number();
2388 dcp::String::compose("libdcp %1", dcp::version),
2389 dcp::String::compose("libdcp %1", dcp::version),
2390 dcp::LocalTime().as_string(),
2394 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2398 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2400 path dir = "build/test/verify_missing_extension_metadata1";
2401 auto dcp = make_simple (dir);
2403 dcp::String::compose("libdcp %1", dcp::version),
2404 dcp::String::compose("libdcp %1", dcp::version),
2405 dcp::LocalTime().as_string(),
2409 auto cpl = dcp->cpls()[0];
2412 Editor e (cpl->file().get());
2413 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2416 check_verify_result (
2419 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2420 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2425 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2427 path dir = "build/test/verify_missing_extension_metadata2";
2428 auto dcp = make_simple (dir);
2430 dcp::String::compose("libdcp %1", dcp::version),
2431 dcp::String::compose("libdcp %1", dcp::version),
2432 dcp::LocalTime().as_string(),
2436 auto cpl = dcp->cpls()[0];
2439 Editor e (cpl->file().get());
2440 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2443 check_verify_result (
2446 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2447 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2452 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2454 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2455 auto dcp = make_simple (dir);
2457 dcp::String::compose("libdcp %1", dcp::version),
2458 dcp::String::compose("libdcp %1", dcp::version),
2459 dcp::LocalTime().as_string(),
2463 auto const cpl = dcp->cpls()[0];
2466 Editor e (cpl->file().get());
2467 e.replace ("<meta:Name>A", "<meta:NameX>A");
2468 e.replace ("n</meta:Name>", "n</meta:NameX>");
2471 check_verify_result (
2474 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2475 { 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 },
2476 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2481 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2483 path dir = "build/test/verify_invalid_extension_metadata1";
2484 auto dcp = make_simple (dir);
2486 dcp::String::compose("libdcp %1", dcp::version),
2487 dcp::String::compose("libdcp %1", dcp::version),
2488 dcp::LocalTime().as_string(),
2492 auto cpl = dcp->cpls()[0];
2495 Editor e (cpl->file().get());
2496 e.replace ("Application", "Fred");
2499 check_verify_result (
2502 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2503 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2508 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2510 path dir = "build/test/verify_invalid_extension_metadata2";
2511 auto dcp = make_simple (dir);
2513 dcp::String::compose("libdcp %1", dcp::version),
2514 dcp::String::compose("libdcp %1", dcp::version),
2515 dcp::LocalTime().as_string(),
2519 auto cpl = dcp->cpls()[0];
2522 Editor e (cpl->file().get());
2523 e.replace ("DCP Constraints Profile", "Fred");
2526 check_verify_result (
2529 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2530 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2535 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2537 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2538 auto dcp = make_simple (dir);
2540 dcp::String::compose("libdcp %1", dcp::version),
2541 dcp::String::compose("libdcp %1", dcp::version),
2542 dcp::LocalTime().as_string(),
2546 auto const cpl = dcp->cpls()[0];
2549 Editor e (cpl->file().get());
2550 e.replace ("<meta:Value>", "<meta:ValueX>");
2551 e.replace ("</meta:Value>", "</meta:ValueX>");
2554 check_verify_result (
2557 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2558 { 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 },
2559 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2564 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2566 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2567 auto dcp = make_simple (dir);
2569 dcp::String::compose("libdcp %1", dcp::version),
2570 dcp::String::compose("libdcp %1", dcp::version),
2571 dcp::LocalTime().as_string(),
2575 auto const cpl = dcp->cpls()[0];
2578 Editor e (cpl->file().get());
2579 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2582 check_verify_result (
2585 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2586 { 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() },
2591 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2593 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2594 auto dcp = make_simple (dir);
2596 dcp::String::compose("libdcp %1", dcp::version),
2597 dcp::String::compose("libdcp %1", dcp::version),
2598 dcp::LocalTime().as_string(),
2602 auto const cpl = dcp->cpls()[0];
2605 Editor e (cpl->file().get());
2606 e.replace ("<meta:Property>", "<meta:PropertyX>");
2607 e.replace ("</meta:Property>", "</meta:PropertyX>");
2610 check_verify_result (
2613 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2614 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2615 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2620 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2622 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2623 auto dcp = make_simple (dir);
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:PropertyList>", "<meta:PropertyListX>");
2636 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2639 check_verify_result (
2642 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2643 { 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 },
2644 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2650 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2652 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2653 prepare_directory (dir);
2654 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2655 copy_file (i.path(), dir / i.path().filename());
2658 path const pkl = dir / ( "pkl_" + encryption_test_pkl_id + ".xml" );
2659 path const cpl = dir / ( "cpl_" + encryption_test_cpl_id + ".xml");
2663 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2666 check_verify_result (
2669 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, encryption_test_cpl_id, canonical(cpl) },
2670 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl), },
2671 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2672 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2673 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2674 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2675 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2676 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, encryption_test_cpl_id, canonical(cpl) }
2681 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2683 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2684 prepare_directory (dir);
2685 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2686 copy_file (i.path(), dir / i.path().filename());
2689 path const cpl = dir / ("cpl_" + encryption_test_cpl_id + ".xml");
2690 path const pkl = dir / ("pkl_" + encryption_test_pkl_id + ".xml");
2693 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2696 check_verify_result (
2699 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, encryption_test_pkl_id, canonical(pkl) },
2700 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2701 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2702 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2703 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2704 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, encryption_test_cpl_id, canonical(cpl) },
2705 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, encryption_test_pkl_id, canonical(pkl) },
2710 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2712 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2713 prepare_directory (dir);
2714 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2715 copy_file (i.path(), dir / i.path().filename());
2719 Editor e (dir / dcp_test1_pkl);
2720 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2723 check_verify_result ({dir}, {});
2727 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2729 path dir ("build/test/verify_must_not_be_partially_encrypted");
2730 prepare_directory (dir);
2734 auto signer = make_shared<dcp::CertificateChain>();
2735 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2736 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2737 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2738 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2740 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
2744 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2747 auto writer = mp->start_write (dir / "video.mxf", false);
2748 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2749 for (int i = 0; i < 24; ++i) {
2750 writer->write (j2c.data(), j2c.size());
2752 writer->finalize ();
2754 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2756 auto reel = make_shared<dcp::Reel>(
2757 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2758 make_shared<dcp::ReelSoundAsset>(ms, 0)
2761 reel->add (simple_markers());
2765 cpl->set_content_version (
2766 {"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"}
2768 cpl->set_annotation_text ("A Test DCP");
2769 cpl->set_issuer ("OpenDCP 0.0.25");
2770 cpl->set_creator ("OpenDCP 0.0.25");
2771 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2772 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2773 cpl->set_main_sound_sample_rate (48000);
2774 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2775 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2776 cpl->set_version_number (1);
2780 d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2782 check_verify_result (
2785 {dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED},
2790 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2792 vector<dcp::VerificationNote> notes;
2793 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"));
2794 auto reader = picture.start_read ();
2795 auto frame = reader->get_frame (0);
2796 verify_j2k (frame, notes);
2797 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2801 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2803 vector<dcp::VerificationNote> notes;
2804 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2805 auto reader = picture.start_read ();
2806 auto frame = reader->get_frame (0);
2807 verify_j2k (frame, notes);
2808 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2812 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2814 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2815 prepare_directory (dir);
2816 auto dcp = make_simple (dir);
2818 vector<dcp::VerificationNote> notes;
2819 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2820 auto reader = picture.start_read ();
2821 auto frame = reader->get_frame (0);
2822 verify_j2k (frame, notes);
2823 BOOST_REQUIRE_EQUAL (notes.size(), 0U);
2827 /** Check that ResourceID and the XML ID being different is spotted */
2828 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_resource_id)
2830 boost::filesystem::path const dir = "build/test/verify_mismatched_subtitle_resource_id";
2831 prepare_directory (dir);
2833 ASDCP::WriterInfo writer_info;
2834 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2837 auto mxf_id = dcp::make_uuid ();
2838 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2839 BOOST_REQUIRE (c == Kumu::UUID_Length);
2841 auto resource_id = dcp::make_uuid ();
2842 ASDCP::TimedText::TimedTextDescriptor descriptor;
2843 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2844 DCP_ASSERT (c == Kumu::UUID_Length);
2846 auto xml_id = dcp::make_uuid ();
2847 ASDCP::TimedText::MXFWriter writer;
2848 auto subs_mxf = dir / "subs.mxf";
2849 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2850 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2851 writer.WriteTimedTextResource (dcp::String::compose(
2852 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2853 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2854 "<Id>urn:uuid:%1</Id>"
2855 "<ContentTitleText>Content</ContentTitleText>"
2856 "<AnnotationText>Annotation</AnnotationText>"
2857 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2858 "<ReelNumber>1</ReelNumber>"
2859 "<Language>en-US</Language>"
2860 "<EditRate>25 1</EditRate>"
2861 "<TimeCodeRate>25</TimeCodeRate>"
2862 "<StartTime>00:00:00:00</StartTime>"
2864 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2865 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2866 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2875 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2876 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2878 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2880 check_verify_result (
2883 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2884 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_RESOURCE_ID },
2885 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2886 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2891 /** Check that ResourceID and the MXF ID being the same is spotted */
2892 BOOST_AUTO_TEST_CASE (verify_incorrect_timed_text_id)
2894 boost::filesystem::path const dir = "build/test/verify_incorrect_timed_text_id";
2895 prepare_directory (dir);
2897 ASDCP::WriterInfo writer_info;
2898 writer_info.LabelSetType = ASDCP::LS_MXF_SMPTE;
2901 auto mxf_id = dcp::make_uuid ();
2902 Kumu::hex2bin (mxf_id.c_str(), writer_info.AssetUUID, Kumu::UUID_Length, &c);
2903 BOOST_REQUIRE (c == Kumu::UUID_Length);
2905 auto resource_id = mxf_id;
2906 ASDCP::TimedText::TimedTextDescriptor descriptor;
2907 Kumu::hex2bin (resource_id.c_str(), descriptor.AssetID, Kumu::UUID_Length, &c);
2908 DCP_ASSERT (c == Kumu::UUID_Length);
2910 auto xml_id = resource_id;
2911 ASDCP::TimedText::MXFWriter writer;
2912 auto subs_mxf = dir / "subs.mxf";
2913 auto r = writer.OpenWrite(subs_mxf.c_str(), writer_info, descriptor, 4096);
2914 BOOST_REQUIRE (ASDCP_SUCCESS(r));
2915 writer.WriteTimedTextResource (dcp::String::compose(
2916 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
2917 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
2918 "<Id>urn:uuid:%1</Id>"
2919 "<ContentTitleText>Content</ContentTitleText>"
2920 "<AnnotationText>Annotation</AnnotationText>"
2921 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
2922 "<ReelNumber>1</ReelNumber>"
2923 "<Language>en-US</Language>"
2924 "<EditRate>25 1</EditRate>"
2925 "<TimeCodeRate>25</TimeCodeRate>"
2926 "<StartTime>00:00:00:00</StartTime>"
2928 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
2929 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
2930 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
2939 auto subs_asset = make_shared<dcp::SMPTESubtitleAsset>(subs_mxf);
2940 auto subs_reel = make_shared<dcp::ReelSMPTESubtitleAsset>(subs_asset, dcp::Fraction(24, 1), 240, 0);
2942 auto cpl = write_dcp_with_single_asset (dir, subs_reel);
2944 check_verify_result (
2947 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION , "240 0", boost::filesystem::canonical(subs_mxf) },
2948 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INCORRECT_TIMED_TEXT_ASSET_ID },
2949 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
2950 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }