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_markers_asset.h"
46 #include "reel_mono_picture_asset.h"
47 #include "reel_sound_asset.h"
48 #include "reel_stereo_picture_asset.h"
49 #include "reel_subtitle_asset.h"
50 #include "smpte_subtitle_asset.h"
51 #include "stereo_picture_asset.h"
52 #include "stream_operators.h"
56 #include "verify_j2k.h"
57 #include <boost/test/unit_test.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 'xX3bMCBdXEOYEpYmsConNWrWUAGs=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
337 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, "value 'xaddO7je2lZSNQp55qjCWo5DLKFQ=' 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::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") },
1327 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_SUBTITLE_LANGUAGES }
1332 BOOST_AUTO_TEST_CASE (verify_multiple_closed_caption_languages_allowed)
1334 path path ("build/test/verify_multiple_closed_caption_languages_allowed");
1335 auto dcp = make_simple (path, 2, 240);
1336 auto cpl = dcp->cpls()[0];
1339 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1340 ccaps->set_language (dcp::LanguageTag("de-DE"));
1341 ccaps->add (simple_subtitle());
1342 ccaps->write (path / "subs1.mxf");
1343 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1344 cpl->reels()[0]->add(reel_ccaps);
1348 auto ccaps = make_shared<dcp::SMPTESubtitleAsset>();
1349 ccaps->set_language (dcp::LanguageTag("en-US"));
1350 ccaps->add (simple_subtitle());
1351 ccaps->write (path / "subs2.mxf");
1352 auto reel_ccaps = make_shared<dcp::ReelClosedCaptionAsset>(ccaps, dcp::Fraction(24, 1), 240, 0);
1353 cpl->reels()[1]->add(reel_ccaps);
1357 dcp::Standard::SMPTE,
1358 dcp::String::compose("libdcp %1", dcp::version),
1359 dcp::String::compose("libdcp %1", dcp::version),
1360 dcp::LocalTime().as_string(),
1364 check_verify_result (
1367 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1368 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1373 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1375 path dir = "build/test/verify_missing_subtitle_start_time";
1376 prepare_directory (dir);
1377 auto dcp = make_simple (dir, 1, 240);
1380 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1381 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1382 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1383 "<ContentTitleText>Content</ContentTitleText>"
1384 "<AnnotationText>Annotation</AnnotationText>"
1385 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1386 "<ReelNumber>1</ReelNumber>"
1387 "<Language>de-DE</Language>"
1388 "<EditRate>25 1</EditRate>"
1389 "<TimeCodeRate>25</TimeCodeRate>"
1390 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1392 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1393 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1394 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1400 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1401 BOOST_REQUIRE (xml_file);
1402 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1404 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1405 subs->write (dir / "subs.mxf");
1407 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1408 dcp->cpls().front()->reels().front()->add(reel_subs);
1410 dcp::Standard::SMPTE,
1411 dcp::String::compose("libdcp %1", dcp::version),
1412 dcp::String::compose("libdcp %1", dcp::version),
1413 dcp::LocalTime().as_string(),
1417 check_verify_result (
1420 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1421 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1426 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1428 path dir = "build/test/verify_invalid_subtitle_start_time";
1429 prepare_directory (dir);
1430 auto dcp = make_simple (dir, 1, 240);
1433 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1434 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1435 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1436 "<ContentTitleText>Content</ContentTitleText>"
1437 "<AnnotationText>Annotation</AnnotationText>"
1438 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1439 "<ReelNumber>1</ReelNumber>"
1440 "<Language>de-DE</Language>"
1441 "<EditRate>25 1</EditRate>"
1442 "<TimeCodeRate>25</TimeCodeRate>"
1443 "<StartTime>00:00:02:00</StartTime>"
1444 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1446 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1447 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1448 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1454 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1455 BOOST_REQUIRE (xml_file);
1456 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1458 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1459 subs->write (dir / "subs.mxf");
1461 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1462 dcp->cpls().front()->reels().front()->add(reel_subs);
1464 dcp::Standard::SMPTE,
1465 dcp::String::compose("libdcp %1", dcp::version),
1466 dcp::String::compose("libdcp %1", dcp::version),
1467 dcp::LocalTime().as_string(),
1471 check_verify_result (
1474 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1475 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1483 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1486 , v_position(v_position_)
1498 shared_ptr<dcp::CPL>
1499 dcp_with_text (path dir, vector<TestText> subs)
1501 prepare_directory (dir);
1502 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1503 asset->set_start_time (dcp::Time());
1504 for (auto i: subs) {
1505 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1507 asset->set_language (dcp::LanguageTag("de-DE"));
1508 asset->write (dir / "subs.mxf");
1510 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1511 return write_dcp_with_single_asset (dir, reel_asset);
1515 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1517 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1518 /* Just too early */
1519 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1520 check_verify_result (
1523 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1524 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1530 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1532 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1533 /* Just late enough */
1534 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1535 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1539 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1541 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1542 prepare_directory (dir);
1544 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1545 asset1->set_start_time (dcp::Time());
1546 /* Just late enough */
1547 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1548 asset1->set_language (dcp::LanguageTag("de-DE"));
1549 asset1->write (dir / "subs1.mxf");
1550 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1551 auto reel1 = make_shared<dcp::Reel>();
1552 reel1->add (reel_asset1);
1553 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1554 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1555 reel1->add (markers1);
1557 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1558 asset2->set_start_time (dcp::Time());
1559 /* This would be too early on first reel but should be OK on the second */
1560 add_test_subtitle (asset2, 0, 4 * 24);
1561 asset2->set_language (dcp::LanguageTag("de-DE"));
1562 asset2->write (dir / "subs2.mxf");
1563 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1564 auto reel2 = make_shared<dcp::Reel>();
1565 reel2->add (reel_asset2);
1566 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1567 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1568 reel2->add (markers2);
1570 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1573 auto dcp = make_shared<dcp::DCP>(dir);
1576 dcp::Standard::SMPTE,
1577 dcp::String::compose("libdcp %1", dcp::version),
1578 dcp::String::compose("libdcp %1", dcp::version),
1579 dcp::LocalTime().as_string(),
1584 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1588 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1590 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1591 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1595 { 5 * 24 + 1, 6 * 24 },
1597 check_verify_result (
1600 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_SPACING },
1601 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1606 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1608 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1609 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1613 { 5 * 24 + 16, 8 * 24 },
1615 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1619 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1621 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1622 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1623 check_verify_result (
1626 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION },
1627 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1632 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1634 auto const dir = path("build/test/verify_valid_subtitle_duration");
1635 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1636 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1640 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1642 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1643 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1646 { 96, 200, 0.0, "We" },
1647 { 96, 200, 0.1, "have" },
1648 { 96, 200, 0.2, "four" },
1649 { 96, 200, 0.3, "lines" }
1651 check_verify_result (
1654 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1655 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1660 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1662 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1663 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1666 { 96, 200, 0.0, "We" },
1667 { 96, 200, 0.1, "have" },
1668 { 96, 200, 0.2, "four" },
1670 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1674 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1676 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1677 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1680 { 96, 300, 0.0, "We" },
1681 { 96, 300, 0.1, "have" },
1682 { 150, 180, 0.2, "four" },
1683 { 150, 180, 0.3, "lines" }
1685 check_verify_result (
1688 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_COUNT },
1689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1694 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1696 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1697 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1700 { 96, 300, 0.0, "We" },
1701 { 96, 300, 0.1, "have" },
1702 { 150, 180, 0.2, "four" },
1703 { 190, 250, 0.3, "lines" }
1705 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1709 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1711 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1712 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1715 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1717 check_verify_result (
1720 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1721 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1726 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1728 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1729 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1732 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1734 check_verify_result (
1737 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INVALID_SUBTITLE_LINE_LENGTH },
1738 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1743 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1745 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1746 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1749 { 96, 200, 0.0, "We" },
1750 { 96, 200, 0.1, "have" },
1751 { 96, 200, 0.2, "four" },
1752 { 96, 200, 0.3, "lines" }
1754 check_verify_result (
1757 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1758 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1763 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1765 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1766 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1769 { 96, 200, 0.0, "We" },
1770 { 96, 200, 0.1, "have" },
1771 { 96, 200, 0.2, "four" },
1773 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1777 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1779 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1780 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1783 { 96, 300, 0.0, "We" },
1784 { 96, 300, 0.1, "have" },
1785 { 150, 180, 0.2, "four" },
1786 { 150, 180, 0.3, "lines" }
1788 check_verify_result (
1791 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_COUNT},
1792 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1797 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1799 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1800 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1803 { 96, 300, 0.0, "We" },
1804 { 96, 300, 0.1, "have" },
1805 { 150, 180, 0.2, "four" },
1806 { 190, 250, 0.3, "lines" }
1808 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1812 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1814 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1815 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1818 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1820 check_verify_result (
1823 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1824 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1829 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1831 path const dir("build/test/verify_invalid_sound_frame_rate");
1832 prepare_directory (dir);
1834 auto picture = simple_picture (dir, "foo");
1835 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1836 auto reel = make_shared<dcp::Reel>();
1837 reel->add (reel_picture);
1838 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1839 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1840 reel->add (reel_sound);
1841 reel->add (simple_markers());
1842 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1844 auto dcp = make_shared<dcp::DCP>(dir);
1847 dcp::Standard::SMPTE,
1848 dcp::String::compose("libdcp %1", dcp::version),
1849 dcp::String::compose("libdcp %1", dcp::version),
1850 dcp::LocalTime().as_string(),
1854 check_verify_result (
1857 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1858 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1863 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1865 path const dir("build/test/verify_missing_cpl_annotation_text");
1866 auto dcp = make_simple (dir);
1868 dcp::Standard::SMPTE,
1869 dcp::String::compose("libdcp %1", dcp::version),
1870 dcp::String::compose("libdcp %1", dcp::version),
1871 dcp::LocalTime().as_string(),
1875 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1877 auto const cpl = dcp->cpls()[0];
1880 BOOST_REQUIRE (cpl->file());
1881 Editor e(cpl->file().get());
1882 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1885 check_verify_result (
1888 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1889 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1894 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1896 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1897 auto dcp = make_simple (dir);
1899 dcp::Standard::SMPTE,
1900 dcp::String::compose("libdcp %1", dcp::version),
1901 dcp::String::compose("libdcp %1", dcp::version),
1902 dcp::LocalTime().as_string(),
1906 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1907 auto const cpl = dcp->cpls()[0];
1910 BOOST_REQUIRE (cpl->file());
1911 Editor e(cpl->file().get());
1912 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1915 check_verify_result (
1918 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1919 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1924 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1926 path const dir("build/test/verify_mismatched_asset_duration");
1927 prepare_directory (dir);
1928 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1929 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1931 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1932 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1934 auto reel = make_shared<dcp::Reel>(
1935 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1936 make_shared<dcp::ReelSoundAsset>(ms, 0)
1939 reel->add (simple_markers());
1944 dcp::Standard::SMPTE,
1945 dcp::String::compose("libdcp %1", dcp::version),
1946 dcp::String::compose("libdcp %1", dcp::version),
1947 dcp::LocalTime().as_string(),
1951 check_verify_result (
1954 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_ASSET_DURATION },
1955 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1962 shared_ptr<dcp::CPL>
1963 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1965 prepare_directory (dir);
1966 auto dcp = make_shared<dcp::DCP>(dir);
1967 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1969 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1970 subs->set_language (dcp::LanguageTag("de-DE"));
1971 subs->set_start_time (dcp::Time());
1972 subs->add (simple_subtitle());
1973 subs->write (dir / "subs.mxf");
1974 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1976 auto reel1 = make_shared<dcp::Reel>(
1977 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1978 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1982 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1985 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1986 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1987 reel1->add (markers1);
1991 auto reel2 = make_shared<dcp::Reel>(
1992 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1993 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1997 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2000 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2001 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2002 reel2->add (markers2);
2008 dcp::Standard::SMPTE,
2009 dcp::String::compose("libdcp %1", dcp::version),
2010 dcp::String::compose("libdcp %1", dcp::version),
2011 dcp::LocalTime().as_string(),
2019 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
2022 path dir ("build/test/missing_main_subtitle_from_some_reels");
2023 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2024 check_verify_result (
2027 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2028 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2034 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2035 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2036 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2040 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2041 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2042 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2048 shared_ptr<dcp::CPL>
2049 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2051 prepare_directory (dir);
2052 auto dcp = make_shared<dcp::DCP>(dir);
2053 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2055 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2056 subs->set_language (dcp::LanguageTag("de-DE"));
2057 subs->set_start_time (dcp::Time());
2058 subs->add (simple_subtitle());
2059 subs->write (dir / "subs.mxf");
2061 auto reel1 = make_shared<dcp::Reel>(
2062 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2063 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2066 for (int i = 0; i < caps_in_reel1; ++i) {
2067 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2070 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2071 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2072 reel1->add (markers1);
2076 auto reel2 = make_shared<dcp::Reel>(
2077 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2078 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2081 for (int i = 0; i < caps_in_reel2; ++i) {
2082 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2085 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2086 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2087 reel2->add (markers2);
2093 dcp::Standard::SMPTE,
2094 dcp::String::compose("libdcp %1", dcp::version),
2095 dcp::String::compose("libdcp %1", dcp::version),
2096 dcp::LocalTime().as_string(),
2104 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2107 path dir ("build/test/mismatched_closed_caption_asset_counts");
2108 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2109 check_verify_result (
2112 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2113 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2118 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2119 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2120 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2124 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2125 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2126 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2133 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2135 prepare_directory (dir);
2136 auto dcp = make_shared<dcp::DCP>(dir);
2137 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2139 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2140 subs->set_language (dcp::LanguageTag("de-DE"));
2141 subs->set_start_time (dcp::Time());
2142 subs->add (simple_subtitle());
2143 subs->write (dir / "subs.mxf");
2144 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2147 auto reel = make_shared<dcp::Reel>(
2148 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2149 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2152 reel->add (reel_text);
2154 reel->add (simple_markers(240));
2160 dcp::Standard::SMPTE,
2161 dcp::String::compose("libdcp %1", dcp::version),
2162 dcp::String::compose("libdcp %1", dcp::version),
2163 dcp::LocalTime().as_string(),
2167 check_verify_result (
2170 { dcp::VerificationNote::Type::BV21_ERROR, code, subs->id() },
2171 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2176 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2178 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2179 "build/test/verify_subtitle_entry_point_must_be_present",
2180 dcp::VerificationNote::Code::MISSING_SUBTITLE_ENTRY_POINT,
2181 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2182 asset->unset_entry_point ();
2186 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2187 "build/test/verify_subtitle_entry_point_must_be_zero",
2188 dcp::VerificationNote::Code::INCORRECT_SUBTITLE_ENTRY_POINT,
2189 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2190 asset->set_entry_point (4);
2194 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2195 "build/test/verify_closed_caption_entry_point_must_be_present",
2196 dcp::VerificationNote::Code::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2197 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2198 asset->unset_entry_point ();
2202 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2203 "build/test/verify_closed_caption_entry_point_must_be_zero",
2204 dcp::VerificationNote::Code::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2205 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2206 asset->set_entry_point (9);
2212 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2216 path const dir("build/test/verify_missing_hash");
2217 auto dcp = make_simple (dir);
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 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2227 auto const cpl = dcp->cpls()[0];
2230 BOOST_REQUIRE (cpl->file());
2231 Editor e(cpl->file().get());
2232 e.replace("<Hash>addO7je2lZSNQp55qjCWo5DLKFQ=</Hash>", "");
2235 check_verify_result (
2238 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2239 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2246 verify_markers_test (
2248 vector<pair<dcp::Marker, dcp::Time>> markers,
2249 vector<dcp::VerificationNote> test_notes
2252 auto dcp = make_simple (dir);
2253 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2254 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2255 for (auto const& i: markers) {
2256 markers_asset->set (i.first, i.second);
2258 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2260 dcp::Standard::SMPTE,
2261 dcp::String::compose("libdcp %1", dcp::version),
2262 dcp::String::compose("libdcp %1", dcp::version),
2263 dcp::LocalTime().as_string(),
2267 check_verify_result ({dir}, test_notes);
2271 BOOST_AUTO_TEST_CASE (verify_markers)
2273 verify_markers_test (
2274 "build/test/verify_markers_all_correct",
2276 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2277 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2278 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2279 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2284 verify_markers_test (
2285 "build/test/verify_markers_missing_ffec",
2287 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2288 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2289 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2292 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }
2295 verify_markers_test (
2296 "build/test/verify_markers_missing_ffmc",
2298 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2299 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2300 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2303 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }
2306 verify_markers_test (
2307 "build/test/verify_markers_missing_ffoc",
2309 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2310 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2311 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2314 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC}
2317 verify_markers_test (
2318 "build/test/verify_markers_missing_lfoc",
2320 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2321 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2322 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2325 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC }
2328 verify_markers_test (
2329 "build/test/verify_markers_incorrect_ffoc",
2331 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2332 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2333 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2334 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2337 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_FFOC, string("3") }
2340 verify_markers_test (
2341 "build/test/verify_markers_incorrect_lfoc",
2343 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2344 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2345 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2346 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2349 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::INCORRECT_LFOC, string("18") }
2354 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2356 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2357 prepare_directory (dir);
2358 auto dcp = make_simple (dir);
2359 auto cpl = dcp->cpls()[0];
2360 cpl->unset_version_number();
2362 dcp::Standard::SMPTE,
2363 dcp::String::compose("libdcp %1", dcp::version),
2364 dcp::String::compose("libdcp %1", dcp::version),
2365 dcp::LocalTime().as_string(),
2369 check_verify_result ({dir}, {{ dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2373 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2375 path dir = "build/test/verify_missing_extension_metadata1";
2376 auto dcp = make_simple (dir);
2378 dcp::Standard::SMPTE,
2379 dcp::String::compose("libdcp %1", dcp::version),
2380 dcp::String::compose("libdcp %1", dcp::version),
2381 dcp::LocalTime().as_string(),
2385 auto cpl = dcp->cpls()[0];
2388 Editor e (cpl->file().get());
2389 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2392 check_verify_result (
2395 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2396 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2401 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2403 path dir = "build/test/verify_missing_extension_metadata2";
2404 auto dcp = make_simple (dir);
2406 dcp::Standard::SMPTE,
2407 dcp::String::compose("libdcp %1", dcp::version),
2408 dcp::String::compose("libdcp %1", dcp::version),
2409 dcp::LocalTime().as_string(),
2413 auto cpl = dcp->cpls()[0];
2416 Editor e (cpl->file().get());
2417 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2420 check_verify_result (
2423 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2424 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2429 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2431 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2432 auto dcp = make_simple (dir);
2434 dcp::Standard::SMPTE,
2435 dcp::String::compose("libdcp %1", dcp::version),
2436 dcp::String::compose("libdcp %1", dcp::version),
2437 dcp::LocalTime().as_string(),
2441 auto const cpl = dcp->cpls()[0];
2444 Editor e (cpl->file().get());
2445 e.replace ("<meta:Name>A", "<meta:NameX>A");
2446 e.replace ("n</meta:Name>", "n</meta:NameX>");
2449 check_verify_result (
2452 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2453 { 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 },
2454 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2459 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2461 path dir = "build/test/verify_invalid_extension_metadata1";
2462 auto dcp = make_simple (dir);
2464 dcp::Standard::SMPTE,
2465 dcp::String::compose("libdcp %1", dcp::version),
2466 dcp::String::compose("libdcp %1", dcp::version),
2467 dcp::LocalTime().as_string(),
2471 auto cpl = dcp->cpls()[0];
2474 Editor e (cpl->file().get());
2475 e.replace ("Application", "Fred");
2478 check_verify_result (
2481 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2482 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2487 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2489 path dir = "build/test/verify_invalid_extension_metadata2";
2490 auto dcp = make_simple (dir);
2492 dcp::Standard::SMPTE,
2493 dcp::String::compose("libdcp %1", dcp::version),
2494 dcp::String::compose("libdcp %1", dcp::version),
2495 dcp::LocalTime().as_string(),
2499 auto cpl = dcp->cpls()[0];
2502 Editor e (cpl->file().get());
2503 e.replace ("DCP Constraints Profile", "Fred");
2506 check_verify_result (
2509 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2510 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2515 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2517 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2518 auto dcp = make_simple (dir);
2520 dcp::Standard::SMPTE,
2521 dcp::String::compose("libdcp %1", dcp::version),
2522 dcp::String::compose("libdcp %1", dcp::version),
2523 dcp::LocalTime().as_string(),
2527 auto const cpl = dcp->cpls()[0];
2530 Editor e (cpl->file().get());
2531 e.replace ("<meta:Value>", "<meta:ValueX>");
2532 e.replace ("</meta:Value>", "</meta:ValueX>");
2535 check_verify_result (
2538 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2539 { 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 },
2540 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2545 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2547 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2548 auto dcp = make_simple (dir);
2550 dcp::Standard::SMPTE,
2551 dcp::String::compose("libdcp %1", dcp::version),
2552 dcp::String::compose("libdcp %1", dcp::version),
2553 dcp::LocalTime().as_string(),
2557 auto const cpl = dcp->cpls()[0];
2560 Editor e (cpl->file().get());
2561 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2564 check_verify_result (
2567 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2568 { 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() },
2573 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2575 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2576 auto dcp = make_simple (dir);
2578 dcp::Standard::SMPTE,
2579 dcp::String::compose("libdcp %1", dcp::version),
2580 dcp::String::compose("libdcp %1", dcp::version),
2581 dcp::LocalTime().as_string(),
2585 auto const cpl = dcp->cpls()[0];
2588 Editor e (cpl->file().get());
2589 e.replace ("<meta:Property>", "<meta:PropertyX>");
2590 e.replace ("</meta:Property>", "</meta:PropertyX>");
2593 check_verify_result (
2596 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2597 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2598 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2603 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2605 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2606 auto dcp = make_simple (dir);
2608 dcp::Standard::SMPTE,
2609 dcp::String::compose("libdcp %1", dcp::version),
2610 dcp::String::compose("libdcp %1", dcp::version),
2611 dcp::LocalTime().as_string(),
2615 auto const cpl = dcp->cpls()[0];
2618 Editor e (cpl->file().get());
2619 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2620 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2623 check_verify_result (
2626 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2627 { 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 },
2628 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2634 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2636 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2637 prepare_directory (dir);
2638 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2639 copy_file (i.path(), dir / i.path().filename());
2642 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2643 path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2644 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2645 path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2649 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2652 check_verify_result (
2655 { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2656 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2657 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2658 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2659 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2660 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2661 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2662 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2667 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2669 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2670 prepare_directory (dir);
2671 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2672 copy_file (i.path(), dir / i.path().filename());
2675 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2676 path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2677 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2678 path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2681 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2684 check_verify_result (
2687 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2688 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE },
2689 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE },
2690 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
2691 { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
2692 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2693 { dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2698 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2700 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2701 prepare_directory (dir);
2702 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2703 copy_file (i.path(), dir / i.path().filename());
2707 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2708 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2711 check_verify_result ({dir}, {});
2715 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2717 path dir ("build/test/verify_must_not_be_partially_encrypted");
2718 prepare_directory (dir);
2722 auto signer = make_shared<dcp::CertificateChain>();
2723 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2724 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2725 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2726 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2728 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2732 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2735 auto writer = mp->start_write (dir / "video.mxf", false);
2736 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2737 for (int i = 0; i < 24; ++i) {
2738 writer->write (j2c.data(), j2c.size());
2740 writer->finalize ();
2742 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2744 auto reel = make_shared<dcp::Reel>(
2745 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2746 make_shared<dcp::ReelSoundAsset>(ms, 0)
2749 reel->add (simple_markers());
2753 cpl->set_content_version (
2754 {"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"}
2756 cpl->set_annotation_text ("A Test DCP");
2757 cpl->set_issuer ("OpenDCP 0.0.25");
2758 cpl->set_creator ("OpenDCP 0.0.25");
2759 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2760 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2761 cpl->set_main_sound_sample_rate (48000);
2762 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2763 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2764 cpl->set_version_number (1);
2768 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);
2770 check_verify_result ({dir}, {{dcp::VerificationNote::Type::BV21_ERROR, dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED}});
2774 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
2776 vector<dcp::VerificationNote> notes;
2777 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"));
2778 auto reader = picture.start_read ();
2779 auto frame = reader->get_frame (0);
2780 verify_j2k (frame, notes);
2785 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
2787 vector<dcp::VerificationNote> notes;
2788 dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
2789 auto reader = picture.start_read ();
2790 auto frame = reader->get_frame (0);
2791 verify_j2k (frame, notes);
2796 BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
2798 boost::filesystem::path dir = "build/test/verify_jpeg2000_codestream_libdcp";
2799 prepare_directory (dir);
2800 auto dcp = make_simple (dir);
2801 dcp->write_xml (dcp::Standard::SMPTE);
2802 vector<dcp::VerificationNote> notes;
2803 dcp::MonoPictureAsset picture (find_file(dir, "video"));
2804 auto reader = picture.start_read ();
2805 auto frame = reader->get_frame (0);
2806 verify_j2k (frame, notes);