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 <boost/test/unit_test.hpp>
56 #include <boost/foreach.hpp>
57 #include <boost/algorithm/string.hpp>
66 using std::make_shared;
67 using boost::optional;
68 using namespace boost::filesystem;
69 using std::shared_ptr;
72 static list<pair<string, optional<path>>> stages;
73 static string const dcp_test1_pkl = "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml";
74 static string const dcp_test1_cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
75 static string const dcp_test1_cpl = "cpl_" + dcp_test1_cpl_id + ".xml";
78 stage (string s, optional<path> p)
80 stages.push_back (make_pair (s, p));
90 prepare_directory (path path)
92 using namespace boost::filesystem;
94 create_directories (path);
99 setup (int reference_number, string verify_test_suffix)
101 auto const dir = dcp::String::compose("build/test/verify_test%1", verify_test_suffix);
102 prepare_directory (dir);
103 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
104 copy_file (i.path(), dir / i.path().filename());
113 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::Standard::SMPTE)
115 auto reel = make_shared<dcp::Reel>();
116 reel->add (reel_asset);
117 reel->add (simple_markers());
119 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
121 auto dcp = make_shared<dcp::DCP>(dir);
125 dcp::String::compose("libdcp %1", dcp::version),
126 dcp::String::compose("libdcp %1", dcp::version),
127 dcp::LocalTime().as_string(),
135 /** Class that can alter a file by searching and replacing strings within it.
136 * On destruction modifies the file whose name was given to the constructor.
144 _content = dcp::file_to_string (_path);
149 auto f = fopen(_path.string().c_str(), "w");
151 fwrite (_content.c_str(), _content.length(), 1, f);
155 void replace (string a, string b)
157 auto old_content = _content;
158 boost::algorithm::replace_all (_content, a, b);
159 BOOST_REQUIRE (_content != old_content);
162 void delete_lines (string from, string to)
164 vector<string> lines;
165 boost::algorithm::split (lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
166 bool deleting = false;
167 auto old_content = _content;
169 for (auto i: lines) {
170 if (i.find(from) != string::npos) {
174 _content += i + "\n";
176 if (deleting && i.find(to) != string::npos) {
180 BOOST_REQUIRE (_content != old_content);
185 std::string _content;
191 dump_notes (vector<dcp::VerificationNote> const & notes)
193 for (auto i: notes) {
194 std::cout << dcp::note_to_string(i) << "\n";
201 check_verify_result (vector<path> dir, vector<dcp::VerificationNote> test_notes)
203 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
204 BOOST_REQUIRE_EQUAL (notes.size(), test_notes.size());
205 for (auto i = 0U; i < notes.size(); ++i) {
206 BOOST_REQUIRE_EQUAL (notes[i], test_notes[i]);
213 check_verify_result_after_replace (string suffix, boost::function<path (string)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
215 auto dir = setup (1, suffix);
218 Editor e (file(suffix));
219 e.replace (from, to);
222 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
224 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
225 auto i = notes.begin();
226 auto j = codes.begin();
227 while (i != notes.end()) {
228 BOOST_CHECK_EQUAL (i->code(), *j);
235 BOOST_AUTO_TEST_CASE (verify_no_error)
238 auto dir = setup (1, "no_error");
239 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
241 path const cpl_file = dir / dcp_test1_cpl;
242 path const pkl_file = dir / dcp_test1_pkl;
243 path const assetmap_file = dir / "ASSETMAP.xml";
245 auto st = stages.begin();
246 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
247 BOOST_REQUIRE (st->second);
248 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
250 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
251 BOOST_REQUIRE (st->second);
252 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
254 BOOST_CHECK_EQUAL (st->first, "Checking reel");
255 BOOST_REQUIRE (!st->second);
257 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
258 BOOST_REQUIRE (st->second);
259 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
261 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
262 BOOST_REQUIRE (st->second);
263 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "video.mxf"));
265 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
266 BOOST_REQUIRE (st->second);
267 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
269 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
270 BOOST_REQUIRE (st->second);
271 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "audio.mxf"));
273 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
274 BOOST_REQUIRE (st->second);
275 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
277 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
278 BOOST_REQUIRE (st->second);
279 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
281 BOOST_REQUIRE (st == stages.end());
283 BOOST_CHECK_EQUAL (notes.size(), 0);
287 BOOST_AUTO_TEST_CASE (verify_incorrect_picture_sound_hash)
289 using namespace boost::filesystem;
291 auto dir = setup (1, "incorrect_picture_sound_hash");
293 auto video_path = path(dir / "video.mxf");
294 auto mod = fopen(video_path.string().c_str(), "r+b");
296 fseek (mod, 4096, SEEK_SET);
298 fwrite (&x, sizeof(x), 1, mod);
301 auto audio_path = path(dir / "audio.mxf");
302 mod = fopen(audio_path.string().c_str(), "r+b");
304 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
305 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
308 dcp::ASDCPErrorSuspender sus;
309 check_verify_result (
312 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_PICTURE_HASH, canonical(video_path) },
313 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_SOUND_HASH, canonical(audio_path) },
318 BOOST_AUTO_TEST_CASE (verify_mismatched_picture_sound_hashes)
320 using namespace boost::filesystem;
322 auto dir = setup (1, "mismatched_picture_sound_hashes");
325 Editor e (dir / dcp_test1_pkl);
326 e.replace ("<Hash>", "<Hash>x");
329 check_verify_result (
332 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
333 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
334 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
335 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xxz+gUPoPMdbFlAewvWIq8BRhBmA=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
336 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xXGhFVrqZqapOJx5Fh2SLjj48Yjg=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
337 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
342 BOOST_AUTO_TEST_CASE (verify_failed_read_content_kind)
344 auto dir = setup (1, "failed_read_content_kind");
347 Editor e (dir / dcp_test1_cpl);
348 e.replace ("<ContentKind>", "<ContentKind>x");
351 check_verify_result (
353 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("Bad content kind 'xtrailer'")}}
362 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_cpl);
370 return dcp::String::compose("build/test/verify_test%1/%2", suffix, dcp_test1_pkl);
376 asset_map (string suffix)
378 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", suffix);
382 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_rate)
384 check_verify_result_after_replace (
385 "invalid_picture_frame_rate", &cpl,
386 "<FrameRate>24 1", "<FrameRate>99 1",
387 { dcp::VerificationNote::MISMATCHED_CPL_HASHES,
388 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
392 BOOST_AUTO_TEST_CASE (verify_missing_asset)
394 auto dir = setup (1, "missing_asset");
395 remove (dir / "video.mxf");
396 check_verify_result (
399 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET, canonical(dir) / "video.mxf" }
404 BOOST_AUTO_TEST_CASE (verify_empty_asset_path)
406 check_verify_result_after_replace (
407 "empty_asset_path", &asset_map,
408 "<Path>video.mxf</Path>", "<Path></Path>",
409 { dcp::VerificationNote::EMPTY_ASSET_PATH }
414 BOOST_AUTO_TEST_CASE (verify_mismatched_standard)
416 check_verify_result_after_replace (
417 "mismatched_standard", &cpl,
418 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
419 { dcp::VerificationNote::MISMATCHED_STANDARD,
420 dcp::VerificationNote::INVALID_XML,
421 dcp::VerificationNote::INVALID_XML,
422 dcp::VerificationNote::INVALID_XML,
423 dcp::VerificationNote::INVALID_XML,
424 dcp::VerificationNote::INVALID_XML,
425 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
430 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_id)
432 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
433 check_verify_result_after_replace (
434 "invalid_xml_cpl_id", &cpl,
435 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
436 { dcp::VerificationNote::INVALID_XML }
441 BOOST_AUTO_TEST_CASE (verify_invalid_xml_issue_date)
443 check_verify_result_after_replace (
444 "invalid_xml_issue_date", &cpl,
445 "<IssueDate>", "<IssueDate>x",
446 { dcp::VerificationNote::INVALID_XML,
447 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
452 BOOST_AUTO_TEST_CASE (verify_invalid_xml_pkl_id)
454 check_verify_result_after_replace (
455 "invalid_xml_pkl_id", &pkl,
456 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
457 { dcp::VerificationNote::INVALID_XML }
462 BOOST_AUTO_TEST_CASE (verify_invalid_xml_asset_map_id)
464 check_verify_result_after_replace (
465 "invalix_xml_asset_map_id", &asset_map,
466 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
467 { dcp::VerificationNote::INVALID_XML }
472 BOOST_AUTO_TEST_CASE (verify_invalid_standard)
475 auto dir = setup (3, "verify_invalid_standard");
476 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
478 path const cpl_file = dir / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
479 path const pkl_file = dir / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
480 path const assetmap_file = dir / "ASSETMAP";
482 auto st = stages.begin();
483 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
484 BOOST_REQUIRE (st->second);
485 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir));
487 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
488 BOOST_REQUIRE (st->second);
489 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
491 BOOST_CHECK_EQUAL (st->first, "Checking reel");
492 BOOST_REQUIRE (!st->second);
494 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
495 BOOST_REQUIRE (st->second);
496 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
498 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
499 BOOST_REQUIRE (st->second);
500 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
502 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
503 BOOST_REQUIRE (st->second);
504 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
506 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
507 BOOST_REQUIRE (st->second);
508 BOOST_CHECK_EQUAL (st->second.get(), canonical(dir / "pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
510 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
511 BOOST_REQUIRE (st->second);
512 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
514 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
515 BOOST_REQUIRE (st->second);
516 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
518 BOOST_REQUIRE (st == stages.end());
520 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
521 auto i = notes.begin ();
522 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
523 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INVALID_STANDARD);
526 /* DCP with a short asset */
527 BOOST_AUTO_TEST_CASE (verify_invalid_duration)
529 auto dir = setup (8, "invalid_duration");
530 check_verify_result (
533 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
534 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
535 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
536 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
537 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
544 dcp_from_frame (dcp::ArrayData const& frame, path dir)
546 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
547 create_directories (dir);
548 auto writer = asset->start_write (dir / "pic.mxf", true);
549 for (int i = 0; i < 24; ++i) {
550 writer->write (frame.data(), frame.size());
554 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
555 return write_dcp_with_single_asset (dir, reel_asset);
559 BOOST_AUTO_TEST_CASE (verify_invalid_picture_frame_size_in_bytes)
561 int const too_big = 1302083 * 2;
563 /* Compress a black image */
564 auto image = black_image ();
565 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
566 BOOST_REQUIRE (frame.size() < too_big);
568 /* Place it in a bigger block with some zero padding at the end */
569 dcp::ArrayData oversized_frame(too_big);
570 memcpy (oversized_frame.data(), frame.data(), frame.size());
571 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
573 path const dir("build/test/verify_invalid_picture_frame_size_in_bytes");
574 prepare_directory (dir);
575 auto cpl = dcp_from_frame (oversized_frame, dir);
577 check_verify_result (
580 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
581 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
586 BOOST_AUTO_TEST_CASE (verify_nearly_invalid_picture_frame_size_in_bytes)
588 int const nearly_too_big = 1302083 * 0.98;
590 /* Compress a black image */
591 auto image = black_image ();
592 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
593 BOOST_REQUIRE (frame.size() < nearly_too_big);
595 /* Place it in a bigger block with some zero padding at the end */
596 dcp::ArrayData oversized_frame(nearly_too_big);
597 memcpy (oversized_frame.data(), frame.data(), frame.size());
598 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
600 path const dir("build/test/verify_nearly_invalid_picture_frame_size_in_bytes");
601 prepare_directory (dir);
602 auto cpl = dcp_from_frame (oversized_frame, dir);
604 check_verify_result (
607 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
608 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
613 BOOST_AUTO_TEST_CASE (verify_valid_picture_frame_size_in_bytes)
615 /* Compress a black image */
616 auto image = black_image ();
617 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
618 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
620 path const dir("build/test/verify_valid_picture_frame_size_in_bytes");
621 prepare_directory (dir);
622 auto cpl = dcp_from_frame (frame, dir);
624 check_verify_result ({ dir }, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
628 BOOST_AUTO_TEST_CASE (verify_valid_interop_subtitles)
630 path const dir("build/test/verify_valid_interop_subtitles");
631 prepare_directory (dir);
632 copy_file ("test/data/subs1.xml", dir / "subs.xml");
633 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
634 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
635 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
637 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD }});
641 BOOST_AUTO_TEST_CASE (verify_invalid_interop_subtitles)
643 using namespace boost::filesystem;
645 path const dir("build/test/verify_invalid_interop_subtitles");
646 prepare_directory (dir);
647 copy_file ("test/data/subs1.xml", dir / "subs.xml");
648 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
649 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
650 write_dcp_with_single_asset (dir, reel_asset, dcp::Standard::INTEROP);
653 Editor e (dir / "subs.xml");
654 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
657 check_verify_result (
660 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
661 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
663 dcp::VerificationNote::VERIFY_ERROR,
664 dcp::VerificationNote::INVALID_XML,
665 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
673 BOOST_AUTO_TEST_CASE (verify_valid_smpte_subtitles)
675 path const dir("build/test/verify_valid_smpte_subtitles");
676 prepare_directory (dir);
677 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
678 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
679 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
680 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
682 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
686 BOOST_AUTO_TEST_CASE (verify_invalid_smpte_subtitles)
688 using namespace boost::filesystem;
690 path const dir("build/test/verify_invalid_smpte_subtitles");
691 prepare_directory (dir);
692 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
693 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
694 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
695 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
697 check_verify_result (
700 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
702 dcp::VerificationNote::VERIFY_ERROR,
703 dcp::VerificationNote::INVALID_XML,
704 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
708 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
709 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
714 BOOST_AUTO_TEST_CASE (verify_external_asset)
716 path const ov_dir("build/test/verify_external_asset");
717 prepare_directory (ov_dir);
719 auto image = black_image ();
720 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
721 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
722 dcp_from_frame (frame, ov_dir);
724 dcp::DCP ov (ov_dir);
727 path const vf_dir("build/test/verify_external_asset_vf");
728 prepare_directory (vf_dir);
730 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
731 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
733 check_verify_result (
736 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET, picture->asset()->id() },
737 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
742 BOOST_AUTO_TEST_CASE (verify_valid_cpl_metadata)
744 path const dir("build/test/verify_valid_cpl_metadata");
745 prepare_directory (dir);
747 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
748 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
749 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
751 auto reel = make_shared<dcp::Reel>();
752 reel->add (reel_asset);
754 reel->add (make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 16 * 24), 0));
755 reel->add (simple_markers(16 * 24));
757 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
759 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
760 cpl->set_main_sound_sample_rate (48000);
761 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
762 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
763 cpl->set_version_number (1);
768 dcp::Standard::SMPTE,
769 dcp::String::compose("libdcp %1", dcp::version),
770 dcp::String::compose("libdcp %1", dcp::version),
771 dcp::LocalTime().as_string(),
777 path find_cpl (path dir)
779 for (auto i: directory_iterator(dir)) {
780 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
785 BOOST_REQUIRE (false);
790 /* DCP with invalid CompositionMetadataAsset */
791 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_bad_tag)
793 using namespace boost::filesystem;
795 path const dir("build/test/verify_invalid_cpl_metadata_bad_tag");
796 prepare_directory (dir);
798 auto reel = make_shared<dcp::Reel>();
799 reel->add (black_picture_asset(dir));
800 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
802 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
803 cpl->set_main_sound_sample_rate (48000);
804 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
805 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
806 cpl->set_version_number (1);
808 reel->add (simple_markers());
813 dcp::Standard::SMPTE,
814 dcp::String::compose("libdcp %1", dcp::version),
815 dcp::String::compose("libdcp %1", dcp::version),
816 dcp::LocalTime().as_string(),
821 Editor e (find_cpl(dir));
822 e.replace ("MainSound", "MainSoundX");
825 check_verify_result (
828 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
829 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
831 dcp::VerificationNote::VERIFY_ERROR,
832 dcp::VerificationNote::INVALID_XML,
833 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
834 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
835 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
836 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
837 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
838 "ExtensionMetadataList?,)'"),
839 canonical(cpl->file().get()),
842 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
847 /* DCP with invalid CompositionMetadataAsset */
848 BOOST_AUTO_TEST_CASE (verify_invalid_cpl_metadata_missing_tag)
850 path const dir("build/test/verify_invalid_cpl_metadata_missing_tag");
851 prepare_directory (dir);
853 auto reel = make_shared<dcp::Reel>();
854 reel->add (black_picture_asset(dir));
855 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
857 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
858 cpl->set_main_sound_sample_rate (48000);
859 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
860 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
865 dcp::Standard::SMPTE,
866 dcp::String::compose("libdcp %1", dcp::version),
867 dcp::String::compose("libdcp %1", dcp::version),
868 dcp::LocalTime().as_string(),
873 Editor e (find_cpl(dir));
874 e.replace ("meta:Width", "meta:WidthX");
877 check_verify_result (
879 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
884 BOOST_AUTO_TEST_CASE (verify_invalid_language1)
886 path const dir("build/test/verify_invalid_language1");
887 prepare_directory (dir);
888 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
889 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
890 asset->_language = "wrong-andbad";
891 asset->write (dir / "subs.mxf");
892 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
893 reel_asset->_language = "badlang";
894 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
896 check_verify_result (
899 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
900 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
901 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
906 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
907 BOOST_AUTO_TEST_CASE (verify_invalid_language2)
909 path const dir("build/test/verify_invalid_language2");
910 prepare_directory (dir);
911 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
912 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
913 asset->_language = "wrong-andbad";
914 asset->write (dir / "subs.mxf");
915 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
916 reel_asset->_language = "badlang";
917 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
919 check_verify_result (
922 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
923 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
924 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
929 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
930 * the release territory.
932 BOOST_AUTO_TEST_CASE (verify_invalid_language3)
934 path const dir("build/test/verify_invalid_language3");
935 prepare_directory (dir);
937 auto picture = simple_picture (dir, "foo");
938 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
939 auto reel = make_shared<dcp::Reel>();
940 reel->add (reel_picture);
941 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
942 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
943 reel->add (reel_sound);
944 reel->add (simple_markers());
946 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
948 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
949 cpl->_additional_subtitle_languages.push_back("andso-is-this");
950 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
951 cpl->set_main_sound_sample_rate (48000);
952 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
953 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
954 cpl->set_version_number (1);
955 cpl->_release_territory = "fred-jim";
956 auto dcp = make_shared<dcp::DCP>(dir);
959 dcp::Standard::SMPTE,
960 dcp::String::compose("libdcp %1", dcp::version),
961 dcp::String::compose("libdcp %1", dcp::version),
962 dcp::LocalTime().as_string(),
966 check_verify_result (
969 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("this-is-wrong") },
970 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("andso-is-this") },
971 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("fred-jim") },
972 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("frobozz") },
978 vector<dcp::VerificationNote>
979 check_picture_size (int width, int height, int frame_rate, bool three_d)
981 using namespace boost::filesystem;
983 path dcp_path = "build/test/verify_picture_test";
984 prepare_directory (dcp_path);
986 shared_ptr<dcp::PictureAsset> mp;
988 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
990 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::Standard::SMPTE);
992 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
994 auto image = black_image (dcp::Size(width, height));
995 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
996 int const length = three_d ? frame_rate * 2 : frame_rate;
997 for (int i = 0; i < length; ++i) {
998 picture_writer->write (j2c.data(), j2c.size());
1000 picture_writer->finalize ();
1002 auto d = make_shared<dcp::DCP>(dcp_path);
1003 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1004 cpl->set_annotation_text ("A Test DCP");
1005 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1006 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1007 cpl->set_main_sound_sample_rate (48000);
1008 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1009 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1010 cpl->set_version_number (1);
1012 auto reel = make_shared<dcp::Reel>();
1015 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1017 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1020 reel->add (simple_markers(frame_rate));
1026 dcp::Standard::SMPTE,
1027 dcp::String::compose("libdcp %1", dcp::version),
1028 dcp::String::compose("libdcp %1", dcp::version),
1029 dcp::LocalTime().as_string(),
1033 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1039 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1041 auto notes = check_picture_size(width, height, frame_rate, three_d);
1043 BOOST_CHECK_EQUAL (notes.size(), 0U);
1049 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1051 auto notes = check_picture_size(width, height, frame_rate, three_d);
1052 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1053 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1054 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_SIZE_IN_PIXELS);
1060 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1062 auto notes = check_picture_size(width, height, frame_rate, three_d);
1063 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1064 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1065 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1071 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1073 auto notes = check_picture_size(width, height, frame_rate, three_d);
1074 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1075 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1076 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1080 BOOST_AUTO_TEST_CASE (verify_picture_size)
1082 using namespace boost::filesystem;
1085 check_picture_size_ok (2048, 858, 24, false);
1086 check_picture_size_ok (2048, 858, 25, false);
1087 check_picture_size_ok (2048, 858, 48, false);
1088 check_picture_size_ok (2048, 858, 24, true);
1089 check_picture_size_ok (2048, 858, 25, true);
1090 check_picture_size_ok (2048, 858, 48, true);
1093 check_picture_size_ok (1998, 1080, 24, false);
1094 check_picture_size_ok (1998, 1080, 25, false);
1095 check_picture_size_ok (1998, 1080, 48, false);
1096 check_picture_size_ok (1998, 1080, 24, true);
1097 check_picture_size_ok (1998, 1080, 25, true);
1098 check_picture_size_ok (1998, 1080, 48, true);
1101 check_picture_size_ok (4096, 1716, 24, false);
1104 check_picture_size_ok (3996, 2160, 24, false);
1106 /* Bad frame size */
1107 check_picture_size_bad_frame_size (2050, 858, 24, false);
1108 check_picture_size_bad_frame_size (2048, 658, 25, false);
1109 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1110 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1112 /* Bad 2K frame rate */
1113 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1114 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1115 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1117 /* Bad 4K frame rate */
1118 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1119 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1122 auto notes = check_picture_size(3996, 2160, 24, true);
1123 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1124 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1125 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1131 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1134 make_shared<dcp::SubtitleString>(
1142 dcp::Time(start_frame, 24, 24),
1143 dcp::Time(end_frame, 24, 24),
1145 dcp::HAlign::CENTER,
1147 dcp::VAlign::CENTER,
1148 dcp::Direction::LTR,
1159 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_xml_size_in_bytes)
1161 path const dir("build/test/verify_invalid_closed_caption_xml_size_in_bytes");
1162 prepare_directory (dir);
1164 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1165 for (int i = 0; i < 2048; ++i) {
1166 add_test_subtitle (asset, i * 24, i * 24 + 20);
1168 asset->set_language (dcp::LanguageTag("de-DE"));
1169 asset->write (dir / "subs.mxf");
1170 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1171 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1173 check_verify_result (
1176 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1178 dcp::VerificationNote::VERIFY_BV21_ERROR,
1179 dcp::VerificationNote::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1181 canonical(dir / "subs.mxf")
1183 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1184 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1190 shared_ptr<dcp::SMPTESubtitleAsset>
1191 make_large_subtitle_asset (path font_file)
1193 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1194 dcp::ArrayData big_fake_font(1024 * 1024);
1195 big_fake_font.write (font_file);
1196 for (int i = 0; i < 116; ++i) {
1197 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1205 verify_timed_text_asset_too_large (string name)
1207 auto const dir = path("build/test") / name;
1208 prepare_directory (dir);
1209 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1210 add_test_subtitle (asset, 0, 20);
1211 asset->set_language (dcp::LanguageTag("de-DE"));
1212 asset->write (dir / "subs.mxf");
1214 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1215 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1217 check_verify_result (
1220 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1221 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1222 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1223 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1224 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1229 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1231 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1232 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1236 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_language)
1238 path dir = "build/test/verify_missing_subtitle_language";
1239 prepare_directory (dir);
1240 auto dcp = make_simple (dir, 1, 240);
1243 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1244 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1245 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1246 "<ContentTitleText>Content</ContentTitleText>"
1247 "<AnnotationText>Annotation</AnnotationText>"
1248 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1249 "<ReelNumber>1</ReelNumber>"
1250 "<EditRate>25 1</EditRate>"
1251 "<TimeCodeRate>25</TimeCodeRate>"
1252 "<StartTime>00:00:00:00</StartTime>"
1253 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1255 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1256 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1257 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1263 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1264 BOOST_REQUIRE (xml_file);
1265 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1267 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1268 subs->write (dir / "subs.mxf");
1270 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1271 dcp->cpls().front()->reels().front()->add(reel_subs);
1273 dcp::Standard::SMPTE,
1274 dcp::String::compose("libdcp %1", dcp::version),
1275 dcp::String::compose("libdcp %1", dcp::version),
1276 dcp::LocalTime().as_string(),
1280 check_verify_result (
1283 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1284 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1289 BOOST_AUTO_TEST_CASE (verify_mismatched_subtitle_languages)
1291 path path ("build/test/verify_mismatched_subtitle_languages");
1292 auto dcp = make_simple (path, 2, 240);
1293 auto cpl = dcp->cpls()[0];
1296 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1297 subs->set_language (dcp::LanguageTag("de-DE"));
1298 subs->add (simple_subtitle());
1299 subs->write (path / "subs1.mxf");
1300 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1301 cpl->reels()[0]->add(reel_subs);
1305 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1306 subs->set_language (dcp::LanguageTag("en-US"));
1307 subs->add (simple_subtitle());
1308 subs->write (path / "subs2.mxf");
1309 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1310 cpl->reels()[1]->add(reel_subs);
1314 dcp::Standard::SMPTE,
1315 dcp::String::compose("libdcp %1", dcp::version),
1316 dcp::String::compose("libdcp %1", dcp::version),
1317 dcp::LocalTime().as_string(),
1321 check_verify_result (
1324 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1325 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_SUBTITLE_LANGUAGES },
1326 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1331 BOOST_AUTO_TEST_CASE (verify_missing_subtitle_start_time)
1333 path dir = "build/test/verify_missing_subtitle_start_time";
1334 prepare_directory (dir);
1335 auto dcp = make_simple (dir, 1, 240);
1338 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1339 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1340 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1341 "<ContentTitleText>Content</ContentTitleText>"
1342 "<AnnotationText>Annotation</AnnotationText>"
1343 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1344 "<ReelNumber>1</ReelNumber>"
1345 "<Language>de-DE</Language>"
1346 "<EditRate>25 1</EditRate>"
1347 "<TimeCodeRate>25</TimeCodeRate>"
1348 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1350 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1351 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1352 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1358 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1359 BOOST_REQUIRE (xml_file);
1360 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1362 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1363 subs->write (dir / "subs.mxf");
1365 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1366 dcp->cpls().front()->reels().front()->add(reel_subs);
1368 dcp::Standard::SMPTE,
1369 dcp::String::compose("libdcp %1", dcp::version),
1370 dcp::String::compose("libdcp %1", dcp::version),
1371 dcp::LocalTime().as_string(),
1375 check_verify_result (
1378 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1379 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1384 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_start_time)
1386 path dir = "build/test/verify_invalid_subtitle_start_time";
1387 prepare_directory (dir);
1388 auto dcp = make_simple (dir, 1, 240);
1391 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1392 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1393 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1394 "<ContentTitleText>Content</ContentTitleText>"
1395 "<AnnotationText>Annotation</AnnotationText>"
1396 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1397 "<ReelNumber>1</ReelNumber>"
1398 "<Language>de-DE</Language>"
1399 "<EditRate>25 1</EditRate>"
1400 "<TimeCodeRate>25</TimeCodeRate>"
1401 "<StartTime>00:00:02:00</StartTime>"
1402 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1404 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1405 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1406 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1412 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1413 BOOST_REQUIRE (xml_file);
1414 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1416 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1417 subs->write (dir / "subs.mxf");
1419 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1420 dcp->cpls().front()->reels().front()->add(reel_subs);
1422 dcp::Standard::SMPTE,
1423 dcp::String::compose("libdcp %1", dcp::version),
1424 dcp::String::compose("libdcp %1", dcp::version),
1425 dcp::LocalTime().as_string(),
1429 check_verify_result (
1432 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1433 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1441 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1444 , v_position(v_position_)
1456 shared_ptr<dcp::CPL>
1457 dcp_with_text (path dir, vector<TestText> subs)
1459 prepare_directory (dir);
1460 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1461 asset->set_start_time (dcp::Time());
1462 for (auto i: subs) {
1463 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1465 asset->set_language (dcp::LanguageTag("de-DE"));
1466 asset->write (dir / "subs.mxf");
1468 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1469 return write_dcp_with_single_asset (dir, reel_asset);
1473 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_first_text_time)
1475 auto const dir = path("build/test/verify_invalid_subtitle_first_text_time");
1476 /* Just too early */
1477 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1478 check_verify_result (
1481 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1482 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1488 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time)
1490 auto const dir = path("build/test/verify_valid_subtitle_first_text_time");
1491 /* Just late enough */
1492 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1493 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1497 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_first_text_time_on_second_reel)
1499 auto const dir = path("build/test/verify_valid_subtitle_first_text_time_on_second_reel");
1500 prepare_directory (dir);
1502 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1503 asset1->set_start_time (dcp::Time());
1504 /* Just late enough */
1505 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1506 asset1->set_language (dcp::LanguageTag("de-DE"));
1507 asset1->write (dir / "subs1.mxf");
1508 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1509 auto reel1 = make_shared<dcp::Reel>();
1510 reel1->add (reel_asset1);
1511 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1512 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1513 reel1->add (markers1);
1515 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1516 asset2->set_start_time (dcp::Time());
1517 /* This would be too early on first reel but should be OK on the second */
1518 add_test_subtitle (asset2, 0, 4 * 24);
1519 asset2->set_language (dcp::LanguageTag("de-DE"));
1520 asset2->write (dir / "subs2.mxf");
1521 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1522 auto reel2 = make_shared<dcp::Reel>();
1523 reel2->add (reel_asset2);
1524 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1525 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1526 reel2->add (markers2);
1528 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1531 auto dcp = make_shared<dcp::DCP>(dir);
1534 dcp::Standard::SMPTE,
1535 dcp::String::compose("libdcp %1", dcp::version),
1536 dcp::String::compose("libdcp %1", dcp::version),
1537 dcp::LocalTime().as_string(),
1542 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1546 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_spacing)
1548 auto const dir = path("build/test/verify_invalid_subtitle_spacing");
1549 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1553 { 5 * 24 + 1, 6 * 24 },
1555 check_verify_result (
1558 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_SPACING },
1559 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1564 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_spacing)
1566 auto const dir = path("build/test/verify_valid_subtitle_spacing");
1567 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1571 { 5 * 24 + 16, 8 * 24 },
1573 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1577 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_duration)
1579 auto const dir = path("build/test/verify_invalid_subtitle_duration");
1580 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1581 check_verify_result (
1584 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_DURATION },
1585 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1590 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_duration)
1592 auto const dir = path("build/test/verify_valid_subtitle_duration");
1593 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1594 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1598 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count1)
1600 auto const dir = path ("build/test/invalid_subtitle_line_count1");
1601 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1604 { 96, 200, 0.0, "We" },
1605 { 96, 200, 0.1, "have" },
1606 { 96, 200, 0.2, "four" },
1607 { 96, 200, 0.3, "lines" }
1609 check_verify_result (
1612 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1613 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1618 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count1)
1620 auto const dir = path ("build/test/verify_valid_subtitle_line_count1");
1621 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1624 { 96, 200, 0.0, "We" },
1625 { 96, 200, 0.1, "have" },
1626 { 96, 200, 0.2, "four" },
1628 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1632 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_count2)
1634 auto const dir = path ("build/test/verify_invalid_subtitle_line_count2");
1635 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1638 { 96, 300, 0.0, "We" },
1639 { 96, 300, 0.1, "have" },
1640 { 150, 180, 0.2, "four" },
1641 { 150, 180, 0.3, "lines" }
1643 check_verify_result (
1646 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1647 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1652 BOOST_AUTO_TEST_CASE (verify_valid_subtitle_line_count2)
1654 auto const dir = path ("build/test/verify_valid_subtitle_line_count2");
1655 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1658 { 96, 300, 0.0, "We" },
1659 { 96, 300, 0.1, "have" },
1660 { 150, 180, 0.2, "four" },
1661 { 190, 250, 0.3, "lines" }
1663 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1667 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length1)
1669 auto const dir = path ("build/test/verify_invalid_subtitle_line_length1");
1670 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1673 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1675 check_verify_result (
1678 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1679 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1684 BOOST_AUTO_TEST_CASE (verify_invalid_subtitle_line_length2)
1686 auto const dir = path ("build/test/verify_invalid_subtitle_line_length2");
1687 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1690 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1692 check_verify_result (
1695 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_LENGTH },
1696 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1701 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count1)
1703 auto const dir = path ("build/test/verify_valid_closed_caption_line_count1");
1704 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1707 { 96, 200, 0.0, "We" },
1708 { 96, 200, 0.1, "have" },
1709 { 96, 200, 0.2, "four" },
1710 { 96, 200, 0.3, "lines" }
1712 check_verify_result (
1715 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1716 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1721 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count2)
1723 auto const dir = path ("build/test/verify_valid_closed_caption_line_count2");
1724 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1727 { 96, 200, 0.0, "We" },
1728 { 96, 200, 0.1, "have" },
1729 { 96, 200, 0.2, "four" },
1731 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1735 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_count3)
1737 auto const dir = path ("build/test/verify_invalid_closed_caption_line_count3");
1738 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1741 { 96, 300, 0.0, "We" },
1742 { 96, 300, 0.1, "have" },
1743 { 150, 180, 0.2, "four" },
1744 { 150, 180, 0.3, "lines" }
1746 check_verify_result (
1749 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1750 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1755 BOOST_AUTO_TEST_CASE (verify_valid_closed_caption_line_count4)
1757 auto const dir = path ("build/test/verify_valid_closed_caption_line_count4");
1758 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1761 { 96, 300, 0.0, "We" },
1762 { 96, 300, 0.1, "have" },
1763 { 150, 180, 0.2, "four" },
1764 { 190, 250, 0.3, "lines" }
1766 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1770 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_line_length)
1772 auto const dir = path ("build/test/verify_invalid_closed_caption_line_length");
1773 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1776 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1778 check_verify_result (
1781 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1782 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1787 BOOST_AUTO_TEST_CASE (verify_invalid_sound_frame_rate)
1789 path const dir("build/test/verify_invalid_sound_frame_rate");
1790 prepare_directory (dir);
1792 auto picture = simple_picture (dir, "foo");
1793 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1794 auto reel = make_shared<dcp::Reel>();
1795 reel->add (reel_picture);
1796 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1797 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1798 reel->add (reel_sound);
1799 reel->add (simple_markers());
1800 auto cpl = make_shared<dcp::CPL>("hello", dcp::ContentKind::TRAILER);
1802 auto dcp = make_shared<dcp::DCP>(dir);
1805 dcp::Standard::SMPTE,
1806 dcp::String::compose("libdcp %1", dcp::version),
1807 dcp::String::compose("libdcp %1", dcp::version),
1808 dcp::LocalTime().as_string(),
1812 check_verify_result (
1815 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1816 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1821 BOOST_AUTO_TEST_CASE (verify_missing_cpl_annotation_text)
1823 path const dir("build/test/verify_missing_cpl_annotation_text");
1824 auto dcp = make_simple (dir);
1826 dcp::Standard::SMPTE,
1827 dcp::String::compose("libdcp %1", dcp::version),
1828 dcp::String::compose("libdcp %1", dcp::version),
1829 dcp::LocalTime().as_string(),
1833 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1835 auto const cpl = dcp->cpls()[0];
1838 BOOST_REQUIRE (cpl->file());
1839 Editor e(cpl->file().get());
1840 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1843 check_verify_result (
1846 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1847 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1852 BOOST_AUTO_TEST_CASE (verify_mismatched_cpl_annotation_text)
1854 path const dir("build/test/verify_mismatched_cpl_annotation_text");
1855 auto dcp = make_simple (dir);
1857 dcp::Standard::SMPTE,
1858 dcp::String::compose("libdcp %1", dcp::version),
1859 dcp::String::compose("libdcp %1", dcp::version),
1860 dcp::LocalTime().as_string(),
1864 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1865 auto const cpl = dcp->cpls()[0];
1868 BOOST_REQUIRE (cpl->file());
1869 Editor e(cpl->file().get());
1870 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1873 check_verify_result (
1876 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1877 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1882 BOOST_AUTO_TEST_CASE (verify_mismatched_asset_duration)
1884 path const dir("build/test/verify_mismatched_asset_duration");
1885 prepare_directory (dir);
1886 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1887 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::ContentKind::TRAILER));
1889 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1890 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1892 auto reel = make_shared<dcp::Reel>(
1893 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1894 make_shared<dcp::ReelSoundAsset>(ms, 0)
1897 reel->add (simple_markers());
1902 dcp::Standard::SMPTE,
1903 dcp::String::compose("libdcp %1", dcp::version),
1904 dcp::String::compose("libdcp %1", dcp::version),
1905 dcp::LocalTime().as_string(),
1909 check_verify_result (
1912 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION },
1913 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1920 shared_ptr<dcp::CPL>
1921 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1923 prepare_directory (dir);
1924 auto dcp = make_shared<dcp::DCP>(dir);
1925 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
1927 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1928 subs->set_language (dcp::LanguageTag("de-DE"));
1929 subs->set_start_time (dcp::Time());
1930 subs->add (simple_subtitle());
1931 subs->write (dir / "subs.mxf");
1932 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1934 auto reel1 = make_shared<dcp::Reel>(
1935 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1936 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1940 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1943 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1944 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1945 reel1->add (markers1);
1949 auto reel2 = make_shared<dcp::Reel>(
1950 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1951 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1955 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1958 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1959 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1960 reel2->add (markers2);
1966 dcp::Standard::SMPTE,
1967 dcp::String::compose("libdcp %1", dcp::version),
1968 dcp::String::compose("libdcp %1", dcp::version),
1969 dcp::LocalTime().as_string(),
1977 BOOST_AUTO_TEST_CASE (verify_missing_main_subtitle_from_some_reels)
1980 path dir ("build/test/missing_main_subtitle_from_some_reels");
1981 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
1982 check_verify_result (
1985 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
1986 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1992 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
1993 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
1994 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1998 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
1999 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2000 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2006 shared_ptr<dcp::CPL>
2007 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2009 prepare_directory (dir);
2010 auto dcp = make_shared<dcp::DCP>(dir);
2011 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2013 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2014 subs->set_language (dcp::LanguageTag("de-DE"));
2015 subs->set_start_time (dcp::Time());
2016 subs->add (simple_subtitle());
2017 subs->write (dir / "subs.mxf");
2019 auto reel1 = make_shared<dcp::Reel>(
2020 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2021 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2024 for (int i = 0; i < caps_in_reel1; ++i) {
2025 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2028 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2029 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2030 reel1->add (markers1);
2034 auto reel2 = make_shared<dcp::Reel>(
2035 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2036 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2039 for (int i = 0; i < caps_in_reel2; ++i) {
2040 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2043 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2044 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2045 reel2->add (markers2);
2051 dcp::Standard::SMPTE,
2052 dcp::String::compose("libdcp %1", dcp::version),
2053 dcp::String::compose("libdcp %1", dcp::version),
2054 dcp::LocalTime().as_string(),
2062 BOOST_AUTO_TEST_CASE (verify_mismatched_closed_caption_asset_counts)
2065 path dir ("build/test/mismatched_closed_caption_asset_counts");
2066 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2067 check_verify_result (
2070 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2071 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2076 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2077 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2078 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2082 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2083 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2084 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2091 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2093 prepare_directory (dir);
2094 auto dcp = make_shared<dcp::DCP>(dir);
2095 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2097 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2098 subs->set_language (dcp::LanguageTag("de-DE"));
2099 subs->set_start_time (dcp::Time());
2100 subs->add (simple_subtitle());
2101 subs->write (dir / "subs.mxf");
2102 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2105 auto reel = make_shared<dcp::Reel>(
2106 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2107 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2110 reel->add (reel_text);
2112 reel->add (simple_markers(240));
2118 dcp::Standard::SMPTE,
2119 dcp::String::compose("libdcp %1", dcp::version),
2120 dcp::String::compose("libdcp %1", dcp::version),
2121 dcp::LocalTime().as_string(),
2125 check_verify_result (
2128 { dcp::VerificationNote::VERIFY_BV21_ERROR, code, subs->id() },
2129 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2134 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2136 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2137 "build/test/verify_subtitle_entry_point_must_be_present",
2138 dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT,
2139 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2140 asset->unset_entry_point ();
2144 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2145 "build/test/verify_subtitle_entry_point_must_be_zero",
2146 dcp::VerificationNote::INCORRECT_SUBTITLE_ENTRY_POINT,
2147 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2148 asset->set_entry_point (4);
2152 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2153 "build/test/verify_closed_caption_entry_point_must_be_present",
2154 dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2155 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2156 asset->unset_entry_point ();
2160 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2161 "build/test/verify_closed_caption_entry_point_must_be_zero",
2162 dcp::VerificationNote::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2163 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2164 asset->set_entry_point (9);
2170 BOOST_AUTO_TEST_CASE (verify_missing_hash)
2174 path const dir("build/test/verify_missing_hash");
2175 auto dcp = make_simple (dir);
2177 dcp::Standard::SMPTE,
2178 dcp::String::compose("libdcp %1", dcp::version),
2179 dcp::String::compose("libdcp %1", dcp::version),
2180 dcp::LocalTime().as_string(),
2184 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2185 auto const cpl = dcp->cpls()[0];
2188 BOOST_REQUIRE (cpl->file());
2189 Editor e(cpl->file().get());
2190 e.replace("<Hash>XGhFVrqZqapOJx5Fh2SLjj48Yjg=</Hash>", "");
2193 check_verify_result (
2196 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2197 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2204 verify_markers_test (
2206 vector<pair<dcp::Marker, dcp::Time>> markers,
2207 vector<dcp::VerificationNote> test_notes
2210 auto dcp = make_simple (dir);
2211 dcp->cpls()[0]->set_content_kind (dcp::ContentKind::FEATURE);
2212 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2213 for (auto const& i: markers) {
2214 markers_asset->set (i.first, i.second);
2216 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2218 dcp::Standard::SMPTE,
2219 dcp::String::compose("libdcp %1", dcp::version),
2220 dcp::String::compose("libdcp %1", dcp::version),
2221 dcp::LocalTime().as_string(),
2225 check_verify_result ({dir}, test_notes);
2229 BOOST_AUTO_TEST_CASE (verify_markers)
2231 verify_markers_test (
2232 "build/test/verify_markers_all_correct",
2234 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2235 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2236 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2237 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2242 verify_markers_test (
2243 "build/test/verify_markers_missing_ffec",
2245 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2246 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2247 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2250 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }
2253 verify_markers_test (
2254 "build/test/verify_markers_missing_ffmc",
2256 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2257 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2258 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2261 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }
2264 verify_markers_test (
2265 "build/test/verify_markers_missing_ffoc",
2267 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2268 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2269 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2272 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC}
2275 verify_markers_test (
2276 "build/test/verify_markers_missing_lfoc",
2278 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2279 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2280 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2283 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }
2286 verify_markers_test (
2287 "build/test/verify_markers_incorrect_ffoc",
2289 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2290 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2291 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2292 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2295 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC, string("3") }
2298 verify_markers_test (
2299 "build/test/verify_markers_incorrect_lfoc",
2301 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2302 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2303 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2304 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2307 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC, string("18") }
2312 BOOST_AUTO_TEST_CASE (verify_missing_cpl_metadata_version_number)
2314 path dir = "build/test/verify_missing_cpl_metadata_version_number";
2315 prepare_directory (dir);
2316 auto dcp = make_simple (dir);
2317 auto cpl = dcp->cpls()[0];
2318 cpl->unset_version_number();
2320 dcp::Standard::SMPTE,
2321 dcp::String::compose("libdcp %1", dcp::version),
2322 dcp::String::compose("libdcp %1", dcp::version),
2323 dcp::LocalTime().as_string(),
2327 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2331 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata1)
2333 path dir = "build/test/verify_missing_extension_metadata1";
2334 auto dcp = make_simple (dir);
2336 dcp::Standard::SMPTE,
2337 dcp::String::compose("libdcp %1", dcp::version),
2338 dcp::String::compose("libdcp %1", dcp::version),
2339 dcp::LocalTime().as_string(),
2343 auto cpl = dcp->cpls()[0];
2346 Editor e (cpl->file().get());
2347 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2350 check_verify_result (
2353 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2354 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2359 BOOST_AUTO_TEST_CASE (verify_missing_extension_metadata2)
2361 path dir = "build/test/verify_missing_extension_metadata2";
2362 auto dcp = make_simple (dir);
2364 dcp::Standard::SMPTE,
2365 dcp::String::compose("libdcp %1", dcp::version),
2366 dcp::String::compose("libdcp %1", dcp::version),
2367 dcp::LocalTime().as_string(),
2371 auto cpl = dcp->cpls()[0];
2374 Editor e (cpl->file().get());
2375 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2378 check_verify_result (
2381 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2382 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2387 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata3)
2389 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata3";
2390 auto dcp = make_simple (dir);
2392 dcp::Standard::SMPTE,
2393 dcp::String::compose("libdcp %1", dcp::version),
2394 dcp::String::compose("libdcp %1", dcp::version),
2395 dcp::LocalTime().as_string(),
2399 auto const cpl = dcp->cpls()[0];
2402 Editor e (cpl->file().get());
2403 e.replace ("<meta:Name>A", "<meta:NameX>A");
2404 e.replace ("n</meta:Name>", "n</meta:NameX>");
2407 check_verify_result (
2410 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2411 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2412 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2417 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata1)
2419 path dir = "build/test/verify_invalid_extension_metadata1";
2420 auto dcp = make_simple (dir);
2422 dcp::Standard::SMPTE,
2423 dcp::String::compose("libdcp %1", dcp::version),
2424 dcp::String::compose("libdcp %1", dcp::version),
2425 dcp::LocalTime().as_string(),
2429 auto cpl = dcp->cpls()[0];
2432 Editor e (cpl->file().get());
2433 e.replace ("Application", "Fred");
2436 check_verify_result (
2439 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2440 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2445 BOOST_AUTO_TEST_CASE (verify_invalid_extension_metadata2)
2447 path dir = "build/test/verify_invalid_extension_metadata2";
2448 auto dcp = make_simple (dir);
2450 dcp::Standard::SMPTE,
2451 dcp::String::compose("libdcp %1", dcp::version),
2452 dcp::String::compose("libdcp %1", dcp::version),
2453 dcp::LocalTime().as_string(),
2457 auto cpl = dcp->cpls()[0];
2460 Editor e (cpl->file().get());
2461 e.replace ("DCP Constraints Profile", "Fred");
2464 check_verify_result (
2467 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2468 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2473 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata6)
2475 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata6";
2476 auto dcp = make_simple (dir);
2478 dcp::Standard::SMPTE,
2479 dcp::String::compose("libdcp %1", dcp::version),
2480 dcp::String::compose("libdcp %1", dcp::version),
2481 dcp::LocalTime().as_string(),
2485 auto const cpl = dcp->cpls()[0];
2488 Editor e (cpl->file().get());
2489 e.replace ("<meta:Value>", "<meta:ValueX>");
2490 e.replace ("</meta:Value>", "</meta:ValueX>");
2493 check_verify_result (
2496 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2497 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 },
2498 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2503 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata7)
2505 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata7";
2506 auto dcp = make_simple (dir);
2508 dcp::Standard::SMPTE,
2509 dcp::String::compose("libdcp %1", dcp::version),
2510 dcp::String::compose("libdcp %1", dcp::version),
2511 dcp::LocalTime().as_string(),
2515 auto const cpl = dcp->cpls()[0];
2518 Editor e (cpl->file().get());
2519 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2522 check_verify_result (
2525 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2526 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2531 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata8)
2533 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata8";
2534 auto dcp = make_simple (dir);
2536 dcp::Standard::SMPTE,
2537 dcp::String::compose("libdcp %1", dcp::version),
2538 dcp::String::compose("libdcp %1", dcp::version),
2539 dcp::LocalTime().as_string(),
2543 auto const cpl = dcp->cpls()[0];
2546 Editor e (cpl->file().get());
2547 e.replace ("<meta:Property>", "<meta:PropertyX>");
2548 e.replace ("</meta:Property>", "</meta:PropertyX>");
2551 check_verify_result (
2554 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2555 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2556 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2561 BOOST_AUTO_TEST_CASE (verify_invalid_xml_cpl_extension_metadata9)
2563 path dir = "build/test/verify_invalid_xml_cpl_extension_metadata9";
2564 auto dcp = make_simple (dir);
2566 dcp::Standard::SMPTE,
2567 dcp::String::compose("libdcp %1", dcp::version),
2568 dcp::String::compose("libdcp %1", dcp::version),
2569 dcp::LocalTime().as_string(),
2573 auto const cpl = dcp->cpls()[0];
2576 Editor e (cpl->file().get());
2577 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2578 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2581 check_verify_result (
2584 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2585 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2586 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2592 BOOST_AUTO_TEST_CASE (verify_unsigned_cpl_with_encrypted_content)
2594 path dir = "build/test/verify_unsigned_cpl_with_encrypted_content";
2595 prepare_directory (dir);
2596 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2597 copy_file (i.path(), dir / i.path().filename());
2600 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2601 path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2602 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2603 path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2607 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2610 check_verify_result (
2613 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2614 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2615 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2616 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2617 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2618 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2619 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2620 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2625 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_encrypted_content)
2627 path dir = "build/test/unsigned_pkl_with_encrypted_content";
2628 prepare_directory (dir);
2629 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2630 copy_file (i.path(), dir / i.path().filename());
2633 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2634 path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2635 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2636 path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2639 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2642 check_verify_result (
2645 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2646 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2647 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2648 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2649 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2650 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2651 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2656 BOOST_AUTO_TEST_CASE (verify_unsigned_pkl_with_unencrypted_content)
2658 path dir = "build/test/verify_unsigned_pkl_with_unencrypted_content";
2659 prepare_directory (dir);
2660 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2661 copy_file (i.path(), dir / i.path().filename());
2665 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2666 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2669 check_verify_result ({dir}, {});
2673 BOOST_AUTO_TEST_CASE (verify_partially_encrypted)
2675 path dir ("build/test/verify_must_not_be_partially_encrypted");
2676 prepare_directory (dir);
2680 auto signer = make_shared<dcp::CertificateChain>();
2681 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2682 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2683 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2684 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2686 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
2690 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
2693 auto writer = mp->start_write (dir / "video.mxf", false);
2694 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2695 for (int i = 0; i < 24; ++i) {
2696 writer->write (j2c.data(), j2c.size());
2698 writer->finalize ();
2700 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2702 auto reel = make_shared<dcp::Reel>(
2703 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2704 make_shared<dcp::ReelSoundAsset>(ms, 0)
2707 reel->add (simple_markers());
2711 cpl->set_content_version (
2712 {"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"}
2714 cpl->set_annotation_text ("A Test DCP");
2715 cpl->set_issuer ("OpenDCP 0.0.25");
2716 cpl->set_creator ("OpenDCP 0.0.25");
2717 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2718 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2719 cpl->set_main_sound_sample_rate (48000);
2720 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2721 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2722 cpl->set_version_number (1);
2726 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);
2728 check_verify_result ({dir}, {{dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PARTIALLY_ENCRYPTED}});