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.
38 #include "reel_mono_picture_asset.h"
39 #include "reel_sound_asset.h"
42 #include "openjpeg_image.h"
43 #include "mono_picture_asset.h"
44 #include "stereo_picture_asset.h"
45 #include "mono_picture_asset_writer.h"
46 #include "interop_subtitle_asset.h"
47 #include "smpte_subtitle_asset.h"
48 #include "reel_closed_caption_asset.h"
49 #include "reel_stereo_picture_asset.h"
50 #include "reel_subtitle_asset.h"
51 #include "reel_markers_asset.h"
52 #include "compose.hpp"
54 #include "raw_convert.h"
55 #include "stream_operators.h"
56 #include <boost/test/unit_test.hpp>
57 #include <boost/foreach.hpp>
58 #include <boost/algorithm/string.hpp>
67 using std::make_shared;
68 using boost::optional;
69 using namespace boost::filesystem;
70 using std::shared_ptr;
73 static list<pair<string, optional<path>>> stages;
74 static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml";
75 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
76 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
79 stage (string s, optional<path> p)
81 stages.push_back (make_pair (s, p));
91 prepare_directory (path path)
93 using namespace boost::filesystem;
95 create_directories (path);
100 setup (int reference_number, string verify_test_suffix)
102 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
103 prepare_directory (dir);
104 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
105 copy_file (i.path(), dir / i.path().filename());
114 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
116 auto reel = make_shared<dcp::Reel>();
117 reel->add (reel_asset);
118 reel->add (simple_markers());
120 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
122 auto dcp = make_shared<dcp::DCP>(dir);
126 dcp::String::compose("libdcp %1", dcp::version),
127 dcp::String::compose("libdcp %1", dcp::version),
128 dcp::LocalTime().as_string(),
136 /** Class that can alter a file by searching and replacing strings within it.
137 * On destruction modifies the file whose name was given to the constructor.
145 _content = dcp::file_to_string (_path);
150 auto f = fopen(_path.string().c_str(), "w");
152 fwrite (_content.c_str(), _content.length(), 1, f);
156 void replace (string a, string b)
158 auto old_content = _content;
159 boost::algorithm::replace_all (_content, a, b);
160 BOOST_REQUIRE (_content != old_content);
163 void delete_lines (string from, string to)
165 vector<string> lines;
166 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
167 bool deleting = false;
168 auto old_content = _content;
170 for (auto i: lines) {
171 if (i.find(from) != string::npos) {
175 _content += i + "\n";
177 if (deleting && i.find(to) != string::npos) {
181 BOOST_REQUIRE (_content != old_content);
186 std::string _content;
192 dump_notes (vector<dcp::VerificationNote> const & notes)
194 for (auto i: notes) {
195 std::cout << dcp::note_to_string(i) << "\n";
202 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
204 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
205 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
206 for (auto i = 0U; i < notes.size(); ++i) {
207 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
214 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
216 auto dir = setup (1, suffix);
219 Editor e (file(suffix));
220 e.replace (from, to);
223 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
225 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
226 auto i = notes.begin();
227 auto j = codes.begin();
228 while (i != notes.end()) {
229 BOOST_CHECK_EQUAL (i->code(), *j);
236 BOOST_AUTO_TEST_CASE (verify_no_error)
239 auto dir = setup (1, "no_error");
240 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
242 path const cpl_file = dir / dcp_test1_cpl;
243 path const pkl_file = dir / dcp_test1_pkl;
244 path const assetmap_file = dir / "ASSETMAP.xml";
246 auto st = stages.begin();
247 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
248 BOOST_REQUIRE (st->second);
249 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
251 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
252 BOOST_REQUIRE (st->second);
253 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
255 BOOST_CHECK_EQUAL (st->first, "Checking reel");
256 BOOST_REQUIRE (!st->second);
258 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
259 BOOST_REQUIRE (st->second);
260 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
262 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
263 BOOST_REQUIRE (st->second);
264 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
266 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
267 BOOST_REQUIRE (st->second);
268 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
270 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
271 BOOST_REQUIRE (st->second);
272 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
274 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
275 BOOST_REQUIRE (st->second);
276 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
278 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
279 BOOST_REQUIRE (st->second);
280 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
282 BOOST_REQUIRE (st == stages.end());
284 BOOST_CHECK_EQUAL (notes.size(), 0);
288 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
290 using namespace boost::filesystem;
292 auto dir = setup (1, "incorrect_picture_sound_hash");
294 auto video_path = path(dir / "video.mxf");
295 auto mod = fopen(video_path.string().c_str(), "r+b");
297 fseek (mod, 4096, SEEK_SET);
299 fwrite (&x, sizeof(x), 1, mod);
302 auto audio_path = path(dir / "audio.mxf");
303 mod = fopen(audio_path.string().c_str(), "r+b");
305 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
306 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
309 dcp::ASDCPErrorSuspender sus;
310 check_verify_result (
313 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_PICTURE_HASH, canonical(video_path) },
314 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INCORRECT_SOUND_HASH, canonical(audio_path) },
319 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
321 using namespace boost::filesystem;
323 auto dir = setup (1, "mismatched_picture_sound_hashes");
326 Editor e (dir / dcp_test1_pkl);
327 e.replace ("<Hash>", "<Hash>x");
330 check_verify_result (
333 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
334 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
335 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
336 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xxz+gUPoPMdbFlAewvWIq8BRhBmA=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xXGhFVrqZqapOJx5Fh2SLjj48Yjg=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
338 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
343 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
345 auto dir = setup (1, "failed_read_content_kind");
348 Editor e (dir / dcp_test1_cpl);
349 e.replace ("<ContentKind>", "<ContentKind>x");
352 check_verify_result (
354 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("Bad content kind 'xtrailer'")}}
363 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
371 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
377 asset_map (string suffix)
379 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
383 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
385 check_verify_result_after_replace (
386 "invalid_picture_frame_rate", &cpl,
387 "<FrameRate>24 1", "<FrameRate>99 1",
388 { dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES,
389 dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE }
393 BOOST_AUTO_TEST_CASE (verify_missing_asset)
395 auto dir = setup (1, "missing_asset");
396 remove (dir / "video.mxf");
397 check_verify_result (
400 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISSING_ASSET, canonical(dir) / "video.mxf" }
405 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
407 check_verify_result_after_replace (
408 "empty_asset_path", &asset_map,
409 "<Path>video.mxf</Path>", "<Path></Path>",
410 { dcp::VerificationNote::Code::EMPTY_ASSET_PATH }
415 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
417 check_verify_result_after_replace (
418 "mismatched_standard", &cpl,
419 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
420 { dcp::VerificationNote::Code::MISMATCHED_STANDARD,
421 dcp::VerificationNote::Code::INVALID_XML,
422 dcp::VerificationNote::Code::INVALID_XML,
423 dcp::VerificationNote::Code::INVALID_XML,
424 dcp::VerificationNote::Code::INVALID_XML,
425 dcp::VerificationNote::Code::INVALID_XML,
426 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
431 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
433 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
434 check_verify_result_after_replace (
435 "invalid_xml_cpl_id", &cpl,
436 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
437 { dcp::VerificationNote::Code::INVALID_XML }
442 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
444 check_verify_result_after_replace (
445 "invalid_xml_issue_date", &cpl,
446 "<IssueDate>", "<IssueDate>x",
447 { dcp::VerificationNote::Code::INVALID_XML,
448 dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES }
453 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
455 check_verify_result_after_replace (
456 "invalid_xml_pkl_id", &pkl,
457 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
458 { dcp::VerificationNote::Code::INVALID_XML }
463 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
465 check_verify_result_after_replace (
466 "invalix_xml_asset_map_id", &asset_map,
467 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
468 { dcp::VerificationNote::Code::INVALID_XML }
473 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
476 auto dir = setup (3, "verify_invalid_standard");
477 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
479 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
480 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
481 path const assetmap_file = dir / "ASSETMAP";
483 auto st = stages.begin();
484 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
485 BOOST_REQUIRE (st->second);
486 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
488 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
489 BOOST_REQUIRE (st->second);
490 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
492 BOOST_CHECK_EQUAL (st->first, "Checking reel");
493 BOOST_REQUIRE (!st->second);
495 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
496 BOOST_REQUIRE (st->second);
497 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
499 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
500 BOOST_REQUIRE (st->second);
501 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
503 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
504 BOOST_REQUIRE (st->second);
505 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
507 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
508 BOOST_REQUIRE (st->second);
509 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
511 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
512 BOOST_REQUIRE (st->second);
513 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
515 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
516 BOOST_REQUIRE (st->second);
517 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
519 BOOST_REQUIRE (st == stages.end());
521 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
522 auto i = notes.begin ();
523 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::Type::BV21_ERROR);
524 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::Code::INVALID_STANDARD);
527 /* DCP with a short asset */
528 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
530 auto dir = setup (8, "invalid_duration");
531 check_verify_result (
534 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
535 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
536 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
537 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
538 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
545 dcp_from_frame (dcp::ArrayData const& frame, path dir)
547 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
548 create_directories (dir);
549 auto writer = asset->start_write (dir / "pic.mxf", true);
550 for (int i = 0; i < 24; ++i) {
551 writer->write (frame.data(), frame.size());
555 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
556 return write_dcp_with_single_asset (dir, reel_asset);
560 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
562 int const too_big = 1302083 * 2;
564 /* Compress a black image */
565 auto image = black_image ();
566 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
567 BOOST_REQUIRE (frame.size() < too_big);
569 /* Place it in a bigger block with some zero padding at the end */
570 dcp::ArrayData oversized_frame(too_big);
571 memcpy (oversized_frame.data(), frame.data(), frame.size());
572 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
574 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
575 prepare_directory (dir);
576 auto cpl = dcp_from_frame (oversized_frame, dir);
578 check_verify_result (
581 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
582 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
587 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
589 int const nearly_too_big = 1302083 * 0.98;
591 /* Compress a black image */
592 auto image = black_image ();
593 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
594 BOOST_REQUIRE (frame.size() < nearly_too_big);
596 /* Place it in a bigger block with some zero padding at the end */
597 dcp::ArrayData oversized_frame(nearly_too_big);
598 memcpy (oversized_frame.data(), frame.data(), frame.size());
599 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
601 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
602 prepare_directory (dir);
603 auto cpl = dcp_from_frame (oversized_frame, dir);
605 check_verify_result (
608 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
609 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
614 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
616 /* Compress a black image */
617 auto image = black_image ();
618 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
619 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
621 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
622 prepare_directory (dir);
623 auto cpl = dcp_from_frame (frame, dir);
625 check_verify_result ({ dir }, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
629 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
631 path const dir("build/test/verify_valid_interop_subtitles");
632 prepare_directory (dir);
633 copy_file ("test/data/subs1.xml", dir / "subs.xml");
634 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
635 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
636 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
638 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD }});
642 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
644 using namespace boost::filesystem;
646 path const dir("build/test/verify_invalid_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::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
651 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
654 Editor e (dir / "subs.xml");
655 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
658 check_verify_result (
661 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_STANDARD },
662 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
664 dcp::VerificationNote::Type::ERROR,
665 dcp::VerificationNote::Code::INVALID_XML,
666 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
674 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
676 path const dir("build/test/verify_valid_smpte_subtitles");
677 prepare_directory (dir);
678 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
679 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
680 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
681 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
683 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
687 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
689 using namespace boost::filesystem;
691 path const dir("build/test/verify_invalid_smpte_subtitles");
692 prepare_directory (dir);
693 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
694 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
695 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
696 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
698 check_verify_result (
701 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
703 dcp::VerificationNote::Type::ERROR,
704 dcp::VerificationNote::Code::INVALID_XML,
705 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
709 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
710 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
715 BOOST_AUTO_TEST_CASE (verify_external_asset)
717 path const ov_dir("build/test/verify_external_asset");
718 prepare_directory (ov_dir);
720 auto image = black_image ();
721 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
722 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
723 dcp_from_frame (frame, ov_dir);
725 dcp::DCP ov (ov_dir);
728 path const vf_dir("build/test/verify_external_asset_vf");
729 prepare_directory (vf_dir);
731 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
732 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
734 check_verify_result (
737 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::EXTERNAL_ASSET, picture->asset()->id() },
738 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
743 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
745 path const dir("build/test/verify_valid_cpl_metadata");
746 prepare_directory (dir);
748 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
749 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
750 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
752 auto reel = make_shared<dcp::Reel>();
753 reel->add (reel_asset);
755 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
756 reel->add (simple_markers(16 * 24));
758 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
760 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
761 cpl->set_main_sound_sample_rate (48000);
762 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
763 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
764 cpl->set_version_number (1);
769 dcp::Standard::SMPTE,
770 dcp::String::compose("libdcp %1", dcp::version),
771 dcp::String::compose("libdcp %1", dcp::version),
772 dcp::LocalTime().as_string(),
778 path find_cpl (path dir)
780 for (auto i: directory_iterator(dir)) {
781 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
786 BOOST_REQUIRE (false);
791 /* DCP with invalid CompositionMetadataAsset */
792 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
794 using namespace boost::filesystem;
796 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
797 prepare_directory (dir);
799 auto reel = make_shared<dcp::Reel>();
800 reel->add (black_picture_asset(dir));
801 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
803 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
804 cpl->set_main_sound_sample_rate (48000);
805 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
806 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
807 cpl->set_version_number (1);
809 reel->add (simple_markers());
814 dcp::Standard::SMPTE,
815 dcp::String::compose("libdcp %1", dcp::version),
816 dcp::String::compose("libdcp %1", dcp::version),
817 dcp::LocalTime().as_string(),
822 Editor e (find_cpl(dir));
823 e.replace ("MainSound", "MainSoundX");
826 check_verify_result (
829 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
830 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
832 dcp::VerificationNote::Type::ERROR,
833 dcp::VerificationNote::Code::INVALID_XML,
834 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
835 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
836 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
837 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
838 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
839 "ExtensionMetadataList?,)'"),
840 canonical(cpl->file().get()),
843 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
848 /* DCP with invalid CompositionMetadataAsset */
849 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
851 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
852 prepare_directory (dir);
854 auto reel = make_shared<dcp::Reel>();
855 reel->add (black_picture_asset(dir));
856 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
858 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
859 cpl->set_main_sound_sample_rate (48000);
860 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
861 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
866 dcp::Standard::SMPTE,
867 dcp::String::compose("libdcp %1", dcp::version),
868 dcp::String::compose("libdcp %1", dcp::version),
869 dcp::LocalTime().as_string(),
874 Editor e (find_cpl(dir));
875 e.replace ("meta:Width", "meta:WidthX");
878 check_verify_result (
880 {{ dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
885 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
887 path const dir("build/test/verify_invalid_language1");
888 prepare_directory (dir);
889 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
890 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
891 asset->_language = "wrong-andbad";
892 asset->write (dir / "subs.mxf");
893 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
894 reel_asset->_language = "badlang";
895 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
897 check_verify_result (
900 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
901 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
902 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
907 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
908 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
910 path const dir("build/test/verify_invalid_language2");
911 prepare_directory (dir);
912 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
913 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
914 asset->_language = "wrong-andbad";
915 asset->write (dir / "subs.mxf");
916 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
917 reel_asset->_language = "badlang";
918 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
920 check_verify_result (
923 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("badlang") },
924 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("wrong-andbad") },
925 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
930 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
931 * the release territory.
933 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
935 path const dir("build/test/verify_invalid_language3");
936 prepare_directory (dir);
938 auto picture = simple_picture (dir, "foo");
939 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
940 auto reel = make_shared<dcp::Reel>();
941 reel->add (reel_picture);
942 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
943 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
944 reel->add (reel_sound);
945 reel->add (simple_markers());
947 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
949 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
950 cpl->_additional_subtitle_languages.push_back("andso-is-this");
951 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
952 cpl->set_main_sound_sample_rate (48000);
953 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
954 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
955 cpl->set_version_number (1);
956 cpl->_release_territory = "fred-jim";
957 auto dcp = make_shared<dcp::DCP>(dir);
960 dcp::Standard::SMPTE,
961 dcp::String::compose("libdcp %1", dcp::version),
962 dcp::String::compose("libdcp %1", dcp::version),
963 dcp::LocalTime().as_string(),
967 check_verify_result (
970 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("this-is-wrong") },
971 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("andso-is-this") },
972 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("fred-jim") },
973 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_LANGUAGE, string("frobozz") },
979 vector<dcp::VerificationNote>
980 check_picture_size (int width, int height, int frame_rate, bool three_d)
982 using namespace boost::filesystem;
984 path dcp_path = "build/test/verify_picture_test";
985 prepare_directory (dcp_path);
987 shared_ptr<dcp::PictureAsset> mp;
989 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
991 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
993 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
995 auto image = black_image (dcp::Size(width, height));
996 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
997 int const length = three_d ? frame_rate * 2 : frame_rate;
998 for (int i = 0; i < length; ++i) {
999 picture_writer->write (j2c.data(), j2c.size());
1001 picture_writer->finalize ();
1003 auto d = make_shared<dcp::DCP>(dcp_path);
1004 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1005 cpl->set_annotation_text ("A Test DCP");
1006 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1007 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1008 cpl->set_main_sound_sample_rate (48000);
1009 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1010 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1011 cpl->set_version_number (1);
1013 auto reel = make_shared<dcp::Reel>();
1016 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1018 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1021 reel->add (simple_markers(frame_rate));
1027 dcp::Standard::SMPTE,
1028 dcp::String::compose("libdcp %1", dcp::version),
1029 dcp::String::compose("libdcp %1", dcp::version),
1030 dcp::LocalTime().as_string(),
1034 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1040 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1042 auto notes = check_picture_size(width, height, frame_rate, three_d);
1044 BOOST_CHECK_EQUAL (notes.size(), 0U);
1050 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1052 auto notes = check_picture_size(width, height, frame_rate, three_d);
1053 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1054 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1055 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_SIZE_IN_PIXELS);
1061 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1063 auto notes = check_picture_size(width, height, frame_rate, three_d);
1064 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1065 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::Type::BV21_ERROR);
1066 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1072 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1074 auto notes = check_picture_size(width, height, frame_rate, three_d);
1075 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1076 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1077 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1081 BOOST_AUTO_TEST_CASE (verify_picture_size)
1083 using namespace boost::filesystem;
1086 check_picture_size_ok (2048, 858, 24, false);
1087 check_picture_size_ok (2048, 858, 25, false);
1088 check_picture_size_ok (2048, 858, 48, false);
1089 check_picture_size_ok (2048, 858, 24, true);
1090 check_picture_size_ok (2048, 858, 25, true);
1091 check_picture_size_ok (2048, 858, 48, true);
1094 check_picture_size_ok (1998, 1080, 24, false);
1095 check_picture_size_ok (1998, 1080, 25, false);
1096 check_picture_size_ok (1998, 1080, 48, false);
1097 check_picture_size_ok (1998, 1080, 24, true);
1098 check_picture_size_ok (1998, 1080, 25, true);
1099 check_picture_size_ok (1998, 1080, 48, true);
1102 check_picture_size_ok (4096, 1716, 24, false);
1105 check_picture_size_ok (3996, 2160, 24, false);
1107 /* Bad frame size */
1108 check_picture_size_bad_frame_size (2050, 858, 24, false);
1109 check_picture_size_bad_frame_size (2048, 658, 25, false);
1110 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1111 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1113 /* Bad 2K frame rate */
1114 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1115 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1116 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1118 /* Bad 4K frame rate */
1119 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1120 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1123 auto notes = check_picture_size(3996, 2160, 24, true);
1124 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1125 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::Type::BV21_ERROR);
1126 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1132 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1135 make_shared<dcp::SubtitleString>(
1143 dcp::Time(start_frame, 24, 24),
1144 dcp::Time(end_frame, 24, 24),
1146 dcp::HAlign::CENTER,
1148 dcp::VAlign::CENTER,
1149 dcp::Direction::LTR,
1160 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1162 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1163 prepare_directory (dir);
1165 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1166 for (int i = 0; i < 2048; ++i) {
1167 add_test_subtitle (asset, i * 24, i * 24 + 20);
1169 asset->set_language (dcp::LanguageTag("de-DE"));
1170 asset->write (dir / "subs.mxf");
1171 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1172 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1174 check_verify_result (
1177 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1179 dcp::VerificationNote::Type::BV21_ERROR,
1180 dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1182 canonical(dir / "subs.mxf")
1184 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1185 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1191 shared_ptr<dcp::SMPTESubtitleAsset>
1192 make_large_subtitle_asset (path font_file)
1194 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1195 dcp::ArrayData big_fake_font(1024 * 1024);
1196 big_fake_font.write (font_file);
1197 for (int i = 0; i < 116; ++i) {
1198 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1206 verify_timed_text_asset_too_large (string name)
1208 auto const dir = path("build/test") / name;
1209 prepare_directory (dir);
1210 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1211 add_test_subtitle (asset, 0, 20);
1212 asset->set_language (dcp::LanguageTag("de-DE"));
1213 asset->write (dir / "subs.mxf");
1215 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1216 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1218 check_verify_result (
1221 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1222 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1223 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1224 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1225 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1230 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1232 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1233 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1237 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1239 path dir = "build/test/verify_missing_subtitle_language";
1240 prepare_directory (dir);
1241 auto dcp = make_simple (dir, 1, 240);
1244 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1245 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1246 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1247 "<ContentTitleText>Content</ContentTitleText>"
1248 "<AnnotationText>Annotation</AnnotationText>"
1249 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1250 "<ReelNumber>1</ReelNumber>"
1251 "<EditRate>25 1</EditRate>"
1252 "<TimeCodeRate>25</TimeCodeRate>"
1253 "<StartTime>00:00:00:00</StartTime>"
1254 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1256 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1257 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1258 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1264 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1265 BOOST_REQUIRE (xml_file);
1266 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1268 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1269 subs->write (dir / "subs.mxf");
1271 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1272 dcp->cpls().front()->reels().front()->add(reel_subs);
1274 dcp::Standard::SMPTE,
1275 dcp::String::compose("libdcp %1", dcp::version),
1276 dcp::String::compose("libdcp %1", dcp::version),
1277 dcp::LocalTime().as_string(),
1281 check_verify_result (
1284 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1285 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1290 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1292 path path ("build/test/verify_mismatched_subtitle_languages");
1293 auto dcp = make_simple (path, 2, 240);
1294 auto cpl = dcp->cpls()[0];
1297 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1298 subs->set_language (dcp::LanguageTag("de-DE"));
1299 subs->add (simple_subtitle());
1300 subs->write (path / "subs1.mxf");
1301 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1302 cpl->reels()[0]->add(reel_subs);
1306 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1307 subs->set_language (dcp::LanguageTag("en-US"));
1308 subs->add (simple_subtitle());
1309 subs->write (path / "subs2.mxf");
1310 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1311 cpl->reels()[1]->add(reel_subs);
1315 dcp::Standard::SMPTE,
1316 dcp::String::compose("libdcp %1", dcp::version),
1317 dcp::String::compose("libdcp %1", dcp::version),
1318 dcp::LocalTime().as_string(),
1322 check_verify_result (
1325 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1326 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES },
1327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1332 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1334 path dir = "build/test/verify_missing_subtitle_start_time";
1335 prepare_directory (dir);
1336 auto dcp = make_simple (dir, 1, 240);
1339 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1340 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1341 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1342 "<ContentTitleText>Content</ContentTitleText>"
1343 "<AnnotationText>Annotation</AnnotationText>"
1344 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1345 "<ReelNumber>1</ReelNumber>"
1346 "<Language>de-DE</Language>"
1347 "<EditRate>25 1</EditRate>"
1348 "<TimeCodeRate>25</TimeCodeRate>"
1349 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1351 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1352 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1353 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1359 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1360 BOOST_REQUIRE (xml_file);
1361 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1363 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1364 subs->write (dir / "subs.mxf");
1366 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1367 dcp->cpls().front()->reels().front()->add(reel_subs);
1369 dcp::Standard::SMPTE,
1370 dcp::String::compose("libdcp %1", dcp::version),
1371 dcp::String::compose("libdcp %1", dcp::version),
1372 dcp::LocalTime().as_string(),
1376 check_verify_result (
1379 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1380 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1385 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1387 path dir = "build/test/verify_invalid_subtitle_start_time";
1388 prepare_directory (dir);
1389 auto dcp = make_simple (dir, 1, 240);
1392 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1393 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1394 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1395 "<ContentTitleText>Content</ContentTitleText>"
1396 "<AnnotationText>Annotation</AnnotationText>"
1397 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1398 "<ReelNumber>1</ReelNumber>"
1399 "<Language>de-DE</Language>"
1400 "<EditRate>25 1</EditRate>"
1401 "<TimeCodeRate>25</TimeCodeRate>"
1402 "<StartTime>00:00:02:00</StartTime>"
1403 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1405 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1406 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1407 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1413 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1414 BOOST_REQUIRE (xml_file);
1415 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1417 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1418 subs->write (dir / "subs.mxf");
1420 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1421 dcp->cpls().front()->reels().front()->add(reel_subs);
1423 dcp::Standard::SMPTE,
1424 dcp::String::compose("libdcp %1", dcp::version),
1425 dcp::String::compose("libdcp %1", dcp::version),
1426 dcp::LocalTime().as_string(),
1430 check_verify_result (
1433 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1434 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1442 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1445 , v_position(v_position_)
1457 shared_ptr<dcp::CPL>
1458 dcp_with_text (path dir, vector<TestText> subs)
1460 prepare_directory (dir);
1461 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1462 asset->set_start_time (dcp::Time());
1463 for (auto i: subs) {
1464 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1466 asset->set_language (dcp::LanguageTag("de-DE"));
1467 asset->write (dir / "subs.mxf");
1469 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1470 return write_dcp_with_single_asset (dir, reel_asset);
1474 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1476 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1477 /* Just too early */
1478 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1479 check_verify_result (
1482 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1483 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1489 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1491 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1492 /* Just late enough */
1493 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1494 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1498 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1500 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1501 prepare_directory (dir);
1503 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1504 asset1->set_start_time (dcp::Time());
1505 /* Just late enough */
1506 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1507 asset1->set_language (dcp::LanguageTag("de-DE"));
1508 asset1->write (dir / "subs1.mxf");
1509 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1510 auto reel1 = make_shared<dcp::Reel>();
1511 reel1->add (reel_asset1);
1512 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1513 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1514 reel1->add (markers1);
1516 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1517 asset2->set_start_time (dcp::Time());
1518 /* This would be too early on first reel but should be OK on the second */
1519 add_test_subtitle (asset2, 0, 4 * 24);
1520 asset2->set_language (dcp::LanguageTag("de-DE"));
1521 asset2->write (dir / "subs2.mxf");
1522 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1523 auto reel2 = make_shared<dcp::Reel>();
1524 reel2->add (reel_asset2);
1525 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1526 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1527 reel2->add (markers2);
1529 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1532 auto dcp = make_shared<dcp::DCP>(dir);
1535 dcp::Standard::SMPTE,
1536 dcp::String::compose("libdcp %1", dcp::version),
1537 dcp::String::compose("libdcp %1", dcp::version),
1538 dcp::LocalTime().as_string(),
1543 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1547 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1549 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1550 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1554 { 5 * 24 + 1, 6 * 24 },
1556 check_verify_result (
1559 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1560 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1565 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1567 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1568 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1572 { 5 * 24 + 16, 8 * 24 },
1574 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1578 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1580 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1581 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1582 check_verify_result (
1585 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1586 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1591 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1593 auto const dir = path("build/test/verify_valid_subtitle_duration");
1594 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1595 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1599 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1601 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1602 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1605 { 96, 200, 0.0, "We" },
1606 { 96, 200, 0.1, "have" },
1607 { 96, 200, 0.2, "four" },
1608 { 96, 200, 0.3, "lines" }
1610 check_verify_result (
1613 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1614 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1619 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1621 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1622 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1625 { 96, 200, 0.0, "We" },
1626 { 96, 200, 0.1, "have" },
1627 { 96, 200, 0.2, "four" },
1629 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1633 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1635 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1636 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1639 { 96, 300, 0.0, "We" },
1640 { 96, 300, 0.1, "have" },
1641 { 150, 180, 0.2, "four" },
1642 { 150, 180, 0.3, "lines" }
1644 check_verify_result (
1647 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1648 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1653 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1655 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1656 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1659 { 96, 300, 0.0, "We" },
1660 { 96, 300, 0.1, "have" },
1661 { 150, 180, 0.2, "four" },
1662 { 190, 250, 0.3, "lines" }
1664 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1668 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1670 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1671 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1674 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1676 check_verify_result (
1679 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1680 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1685 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1687 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1688 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1691 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1693 check_verify_result (
1696 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1697 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1702 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1704 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1705 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1708 { 96, 200, 0.0, "We" },
1709 { 96, 200, 0.1, "have" },
1710 { 96, 200, 0.2, "four" },
1711 { 96, 200, 0.3, "lines" }
1713 check_verify_result (
1716 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1717 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1722 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1724 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1725 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1728 { 96, 200, 0.0, "We" },
1729 { 96, 200, 0.1, "have" },
1730 { 96, 200, 0.2, "four" },
1732 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1736 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1738 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1739 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1742 { 96, 300, 0.0, "We" },
1743 { 96, 300, 0.1, "have" },
1744 { 150, 180, 0.2, "four" },
1745 { 150, 180, 0.3, "lines" }
1747 check_verify_result (
1750 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1751 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1756 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1758 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1759 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1762 { 96, 300, 0.0, "We" },
1763 { 96, 300, 0.1, "have" },
1764 { 150, 180, 0.2, "four" },
1765 { 190, 250, 0.3, "lines" }
1767 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1771 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1773 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1774 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1777 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1779 check_verify_result (
1782 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1783 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1788 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1790 path const dir("build/test/verify_invalid_sound_frame_rate");
1791 prepare_directory (dir);
1793 auto picture = simple_picture (dir, "foo");
1794 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1795 auto reel = make_shared<dcp::Reel>();
1796 reel->add (reel_picture);
1797 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1798 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1799 reel->add (reel_sound);
1800 reel->add (simple_markers());
1801 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1803 auto dcp = make_shared<dcp::DCP>(dir);
1806 dcp::Standard::SMPTE,
1807 dcp::String::compose("libdcp %1", dcp::version),
1808 dcp::String::compose("libdcp %1", dcp::version),
1809 dcp::LocalTime().as_string(),
1813 check_verify_result (
1816 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1817 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1822 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1824 path const dir("build/test/verify_missing_cpl_annotation_text");
1825 auto dcp = make_simple (dir);
1827 dcp::Standard::SMPTE,
1828 dcp::String::compose("libdcp %1", dcp::version),
1829 dcp::String::compose("libdcp %1", dcp::version),
1830 dcp::LocalTime().as_string(),
1834 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1836 auto const cpl = dcp->cpls()[0];
1839 BOOST_REQUIRE (cpl->file());
1840 Editor e(cpl->file().get());
1841 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1844 check_verify_result (
1847 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1848 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1853 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1855 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1856 auto dcp = make_simple (dir);
1858 dcp::Standard::SMPTE,
1859 dcp::String::compose("libdcp %1", dcp::version),
1860 dcp::String::compose("libdcp %1", dcp::version),
1861 dcp::LocalTime().as_string(),
1865 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1866 auto const cpl = dcp->cpls()[0];
1869 BOOST_REQUIRE (cpl->file());
1870 Editor e(cpl->file().get());
1871 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1874 check_verify_result (
1877 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1878 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1883 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1885 path const dir("build/test/verify_mismatched_asset_duration");
1886 prepare_directory (dir);
1887 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1888 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1890 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1891 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1893 auto reel = make_shared<dcp::Reel>(
1894 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1895 make_shared<dcp::ReelSoundAsset>(ms, 0)
1898 reel->add (simple_markers());
1903 dcp::Standard::SMPTE,
1904 dcp::String::compose("libdcp %1", dcp::version),
1905 dcp::String::compose("libdcp %1", dcp::version),
1906 dcp::LocalTime().as_string(),
1910 check_verify_result (
1913 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1914 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1921 shared_ptr<dcp::CPL>
1922 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1924 prepare_directory (dir);
1925 auto dcp = make_shared<dcp::DCP>(dir);
1926 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1928 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1929 subs->set_language (dcp::LanguageTag("de-DE"));
1930 subs->set_start_time (dcp::Time());
1931 subs->add (simple_subtitle());
1932 subs->write (dir / "subs.mxf");
1933 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1935 auto reel1 = make_shared<dcp::Reel>(
1936 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1937 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1941 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1944 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1945 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1946 reel1->add (markers1);
1950 auto reel2 = make_shared<dcp::Reel>(
1951 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1952 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1956 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1959 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1960 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1961 reel2->add (markers2);
1967 dcp::Standard::SMPTE,
1968 dcp::String::compose("libdcp %1", dcp::version),
1969 dcp::String::compose("libdcp %1", dcp::version),
1970 dcp::LocalTime().as_string(),
1978 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
1981 path dir ("build/test/missing_main_subtitle_from_some_reels");
1982 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
1983 check_verify_result (
1986 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
1987 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1993 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
1994 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
1995 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1999 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2000 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2001 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2007 shared_ptr<dcp::CPL>
2008 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2010 prepare_directory (dir);
2011 auto dcp = make_shared<dcp::DCP>(dir);
2012 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2014 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2015 subs->set_language (dcp::LanguageTag("de-DE"));
2016 subs->set_start_time (dcp::Time());
2017 subs->add (simple_subtitle());
2018 subs->write (dir / "subs.mxf");
2020 auto reel1 = make_shared<dcp::Reel>(
2021 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2022 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2025 for (int i = 0; i < caps_in_reel1; ++i) {
2026 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2029 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2030 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2031 reel1->add (markers1);
2035 auto reel2 = make_shared<dcp::Reel>(
2036 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2037 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2040 for (int i = 0; i < caps_in_reel2; ++i) {
2041 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2044 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2045 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2046 reel2->add (markers2);
2052 dcp::Standard::SMPTE,
2053 dcp::String::compose("libdcp %1", dcp::version),
2054 dcp::String::compose("libdcp %1", dcp::version),
2055 dcp::LocalTime().as_string(),
2063 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2066 path dir ("build/test/mismatched_closed_caption_asset_counts");
2067 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2068 check_verify_result (
2071 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2072 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2077 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2078 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2079 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2083 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2084 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2085 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2092 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2094 prepare_directory (dir);
2095 auto dcp = make_shared<dcp::DCP>(dir);
2096 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2098 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2099 subs->set_language (dcp::LanguageTag("de-DE"));
2100 subs->set_start_time (dcp::Time());
2101 subs->add (simple_subtitle());
2102 subs->write (dir / "subs.mxf");
2103 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2106 auto reel = make_shared<dcp::Reel>(
2107 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2108 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2111 reel->add (reel_text);
2113 reel->add (simple_markers(240));
2119 dcp::Standard::SMPTE,
2120 dcp::String::compose("libdcp %1", dcp::version),
2121 dcp::String::compose("libdcp %1", dcp::version),
2122 dcp::LocalTime().as_string(),
2126 check_verify_result (
2129 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2130 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2135 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2137 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2138 "build/test/verify_subtitle_entry_point_must_be_present",
2139 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2140 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2141 asset->unset_entry_point ();
2145 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2146 "build/test/verify_subtitle_entry_point_must_be_zero",
2147 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2148 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2149 asset->set_entry_point (4);
2153 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2154 "build/test/verify_closed_caption_entry_point_must_be_present",
2155 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2156 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2157 asset->unset_entry_point ();
2161 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2162 "build/test/verify_closed_caption_entry_point_must_be_zero",
2163 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2164 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2165 asset->set_entry_point (9);
2171 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2175 path const dir("build/test/verify_missing_hash");
2176 auto dcp = make_simple (dir);
2178 dcp::Standard::SMPTE,
2179 dcp::String::compose("libdcp %1", dcp::version),
2180 dcp::String::compose("libdcp %1", dcp::version),
2181 dcp::LocalTime().as_string(),
2185 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2186 auto const cpl = dcp->cpls()[0];
2189 BOOST_REQUIRE (cpl->file());
2190 Editor e(cpl->file().get());
2191 e.replace("<Hash>XGhFVrqZqapOJx5Fh2SLjj48Yjg=</Hash>", "");
2194 check_verify_result (
2197 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2198 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2205 verify_markers_test (
2207 vector<pair<dcp::Marker, dcp::Time>> markers,
2208 vector<dcp::VerificationNote> test_notes
2211 auto dcp = make_simple (dir);
2212 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2213 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2214 for (auto const& i: markers) {
2215 markers_asset->set (i.first, i.second);
2217 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2219 dcp::Standard::SMPTE,
2220 dcp::String::compose("libdcp %1", dcp::version),
2221 dcp::String::compose("libdcp %1", dcp::version),
2222 dcp::LocalTime().as_string(),
2226 check_verify_result ({dir}, test_notes);
2230 BOOST_AUTO_TEST_CASE (verify_markers)
2232 verify_markers_test (
2233 "build/test/verify_markers_all_correct",
2235 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2236 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2237 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2238 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2243 verify_markers_test (
2244 "build/test/verify_markers_missing_ffec",
2246 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2247 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2248 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2251 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2254 verify_markers_test (
2255 "build/test/verify_markers_missing_ffmc",
2257 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2258 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2259 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2262 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2265 verify_markers_test (
2266 "build/test/verify_markers_missing_ffoc",
2268 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2269 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2270 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2273 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2276 verify_markers_test (
2277 "build/test/verify_markers_missing_lfoc",
2279 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2280 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2281 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2284 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2287 verify_markers_test (
2288 "build/test/verify_markers_incorrect_ffoc",
2290 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2291 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2292 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2293 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2296 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2299 verify_markers_test (
2300 "build/test/verify_markers_incorrect_lfoc",
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(18, 24, 24) }
2308 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2313 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2315 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2316 prepare_directory (dir);
2317 auto dcp = make_simple (dir);
2318 auto cpl = dcp->cpls()[0];
2319 cpl->unset_version_number();
2321 dcp::Standard::SMPTE,
2322 dcp::String::compose("libdcp %1", dcp::version),
2323 dcp::String::compose("libdcp %1", dcp::version),
2324 dcp::LocalTime().as_string(),
2328 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2332 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2334 path dir = "build/test/verify_missing_extension_metadata1";
2335 auto dcp = make_simple (dir);
2337 dcp::Standard::SMPTE,
2338 dcp::String::compose("libdcp %1", dcp::version),
2339 dcp::String::compose("libdcp %1", dcp::version),
2340 dcp::LocalTime().as_string(),
2344 auto cpl = dcp->cpls()[0];
2347 Editor e (cpl->file().get());
2348 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2351 check_verify_result (
2354 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2355 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2360 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2362 path dir = "build/test/verify_missing_extension_metadata2";
2363 auto dcp = make_simple (dir);
2365 dcp::Standard::SMPTE,
2366 dcp::String::compose("libdcp %1", dcp::version),
2367 dcp::String::compose("libdcp %1", dcp::version),
2368 dcp::LocalTime().as_string(),
2372 auto cpl = dcp->cpls()[0];
2375 Editor e (cpl->file().get());
2376 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2379 check_verify_result (
2382 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2383 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2388 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2390 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2391 auto dcp = make_simple (dir);
2393 dcp::Standard::SMPTE,
2394 dcp::String::compose("libdcp %1", dcp::version),
2395 dcp::String::compose("libdcp %1", dcp::version),
2396 dcp::LocalTime().as_string(),
2400 auto const cpl = dcp->cpls()[0];
2403 Editor e (cpl->file().get());
2404 e.replace ("<meta:Name>A", "<meta:NameX>A");
2405 e.replace ("n</meta:Name>", "n</meta:NameX>");
2408 check_verify_result (
2411 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2412 { 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 },
2413 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2418 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2420 path dir = "build/test/verify_invalid_extension_metadata1";
2421 auto dcp = make_simple (dir);
2423 dcp::Standard::SMPTE,
2424 dcp::String::compose("libdcp %1", dcp::version),
2425 dcp::String::compose("libdcp %1", dcp::version),
2426 dcp::LocalTime().as_string(),
2430 auto cpl = dcp->cpls()[0];
2433 Editor e (cpl->file().get());
2434 e.replace ("Application", "Fred");
2437 check_verify_result (
2440 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2441 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2446 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2448 path dir = "build/test/verify_invalid_extension_metadata2";
2449 auto dcp = make_simple (dir);
2451 dcp::Standard::SMPTE,
2452 dcp::String::compose("libdcp %1", dcp::version),
2453 dcp::String::compose("libdcp %1", dcp::version),
2454 dcp::LocalTime().as_string(),
2458 auto cpl = dcp->cpls()[0];
2461 Editor e (cpl->file().get());
2462 e.replace ("DCP Constraints Profile", "Fred");
2465 check_verify_result (
2468 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2469 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2474 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2476 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2477 auto dcp = make_simple (dir);
2479 dcp::Standard::SMPTE,
2480 dcp::String::compose("libdcp %1", dcp::version),
2481 dcp::String::compose("libdcp %1", dcp::version),
2482 dcp::LocalTime().as_string(),
2486 auto const cpl = dcp->cpls()[0];
2489 Editor e (cpl->file().get());
2490 e.replace ("<meta:Value>", "<meta:ValueX>");
2491 e.replace ("</meta:Value>", "</meta:ValueX>");
2494 check_verify_result (
2497 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2498 { 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 },
2499 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2504 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2506 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2507 auto dcp = make_simple (dir);
2509 dcp::Standard::SMPTE,
2510 dcp::String::compose("libdcp %1", dcp::version),
2511 dcp::String::compose("libdcp %1", dcp::version),
2512 dcp::LocalTime().as_string(),
2516 auto const cpl = dcp->cpls()[0];
2519 Editor e (cpl->file().get());
2520 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2523 check_verify_result (
2526 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2527 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2532 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2534 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2535 auto dcp = make_simple (dir);
2537 dcp::Standard::SMPTE,
2538 dcp::String::compose("libdcp %1", dcp::version),
2539 dcp::String::compose("libdcp %1", dcp::version),
2540 dcp::LocalTime().as_string(),
2544 auto const cpl = dcp->cpls()[0];
2547 Editor e (cpl->file().get());
2548 e.replace ("<meta:Property>", "<meta:PropertyX>");
2549 e.replace ("</meta:Property>", "</meta:PropertyX>");
2552 check_verify_result (
2555 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2556 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2557 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2562 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2564 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2565 auto dcp = make_simple (dir);
2567 dcp::Standard::SMPTE,
2568 dcp::String::compose("libdcp %1", dcp::version),
2569 dcp::String::compose("libdcp %1", dcp::version),
2570 dcp::LocalTime().as_string(),
2574 auto const cpl = dcp->cpls()[0];
2577 Editor e (cpl->file().get());
2578 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2579 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2582 check_verify_result (
2585 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2586 { 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 },
2587 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2593 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2595 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2596 prepare_directory (dir);
2597 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2598 copy_file (i.path(), dir / i.path().filename());
2601 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2602 path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2603 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2604 path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2608 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2611 check_verify_result (
2614 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2615 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2616 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2617 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2618 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2619 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2620 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2621 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2626 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2628 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2629 prepare_directory (dir);
2630 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2631 copy_file (i.path(), dir / i.path().filename());
2634 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2635 path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2636 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2637 path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2640 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2643 check_verify_result (
2646 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2647 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2648 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2649 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2650 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2651 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2652 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2657 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2659 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2660 prepare_directory (dir);
2661 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2662 copy_file (i.path(), dir / i.path().filename());
2666 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2667 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2670 check_verify_result ({dir}, {});
2674 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2676 path dir ("build/test/verify_must_not_be_partially_encrypted");
2677 prepare_directory (dir);
2681 auto signer = make_shared<dcp::CertificateChain>();
2682 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2683 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2684 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2685 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2687 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2691 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2694 auto writer = mp->start_write (dir / "video.mxf", false);
2695 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2696 for (int i = 0; i < 24; ++i) {
2697 writer->write (j2c.data(), j2c.size());
2699 writer->finalize ();
2701 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2703 auto reel = make_shared<dcp::Reel>(
2704 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2705 make_shared<dcp::ReelSoundAsset>(ms, 0)
2708 reel->add (simple_markers());
2712 cpl->set_content_version (
2713 {"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"}
2715 cpl->set_annotation_text ("A Test DCP");
2716 cpl->set_issuer ("OpenDCP 0.0.25");
2717 cpl->set_creator ("OpenDCP 0.0.25");
2718 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2719 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2720 cpl->set_main_sound_sample_rate (48000);
2721 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2722 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2723 cpl->set_version_number (1);
2727 d.write_xml (dcp::Standard::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2729 check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});