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, int verify_test_number)
101 prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
102 for (auto i: directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
103 copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename());
106 return { dcp::String::compose("build/test/verify_test%1", verify_test_number) };
113 write_dcp_with_single_asset (path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::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::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 (int n, boost::function<path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
215 auto directories = setup (1, n);
219 e.replace (from, to);
222 auto notes = dcp::verify (directories, &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 /* Check DCP as-is (should be OK) */
236 BOOST_AUTO_TEST_CASE (verify_test1)
239 auto directories = setup (1, 1);
240 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
242 path const cpl_file = path("build") / "test" / "verify_test1" / dcp_test1_cpl;
243 path const pkl_file = path("build") / "test" / "verify_test1" / dcp_test1_pkl;
244 path const assetmap_file = "build/test/verify_test1/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("build/test/verify_test1"));
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("build/test/verify_test1/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("build/test/verify_test1/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("build/test/verify_test1/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("build/test/verify_test1/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);
287 /* Corrupt the MXFs and check that this is spotted */
288 BOOST_AUTO_TEST_CASE (verify_test2)
290 using namespace boost::filesystem;
292 auto directories = setup (1, 2);
294 auto video_path = path("build/test/verify_test2/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("build/test/verify_test2/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::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_PICTURE_HASH, canonical(video_path) },
314 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INCORRECT_SOUND_HASH, canonical(audio_path) },
318 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
319 BOOST_AUTO_TEST_CASE (verify_test3)
321 using namespace boost::filesystem;
323 auto directories = setup (1, 3);
325 path const dir = path("build") / "test" / "verify_test3";
328 Editor e (dir / dcp_test1_pkl);
329 e.replace ("<Hash>", "<Hash>x");
332 check_verify_result (
335 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, dcp_test1_cpl_id, canonical(dir / dcp_test1_cpl) },
336 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_PICTURE_HASHES, canonical(dir / "video.mxf") },
337 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_SOUND_HASHES, canonical(dir / "audio.mxf") },
338 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xxz+gUPoPMdbFlAewvWIq8BRhBmA=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 12 },
339 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xXGhFVrqZqapOJx5Fh2SLjj48Yjg=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 19 },
340 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, "value 'xqtXbkcwhUj/yqquVLmV+wbzbxQ8=' is invalid Base64-encoded binary", canonical(dir / dcp_test1_pkl), 26 }
344 /* Corrupt the ContentKind in the CPL */
345 BOOST_AUTO_TEST_CASE (verify_test4)
347 auto directories = setup (1, 4);
350 Editor e (path("build") / "test" / "verify_test4" / dcp_test1_cpl);
351 e.replace ("<ContentKind>", "<ContentKind>x");
354 check_verify_result (
356 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("Bad content kind 'xtrailer'")}}
364 return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_cpl);
371 return dcp::String::compose("build/test/verify_test%1/%2", n, dcp_test1_pkl);
378 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
383 BOOST_AUTO_TEST_CASE (verify_test5)
385 check_verify_result_after_replace (
387 "<FrameRate>24 1", "<FrameRate>99 1",
388 { dcp::VerificationNote::MISMATCHED_CPL_HASHES,
389 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
394 BOOST_AUTO_TEST_CASE (verify_test6)
396 auto directories = setup (1, 6);
398 path dir = "build/test/verify_test6";
399 remove (dir / "video.mxf");
400 check_verify_result (
403 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET, canonical(dir) / "video.mxf" }
411 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
414 /* Empty asset filename in ASSETMAP */
415 BOOST_AUTO_TEST_CASE (verify_test7)
417 check_verify_result_after_replace (
419 "<Path>video.mxf</Path>", "<Path></Path>",
420 { dcp::VerificationNote::EMPTY_ASSET_PATH }
424 /* Mismatched standard */
425 BOOST_AUTO_TEST_CASE (verify_test8)
427 check_verify_result_after_replace (
429 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
430 { dcp::VerificationNote::MISMATCHED_STANDARD,
431 dcp::VerificationNote::INVALID_XML,
432 dcp::VerificationNote::INVALID_XML,
433 dcp::VerificationNote::INVALID_XML,
434 dcp::VerificationNote::INVALID_XML,
435 dcp::VerificationNote::INVALID_XML,
436 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
440 /* Badly formatted <Id> in CPL */
441 BOOST_AUTO_TEST_CASE (verify_test9)
443 /* There's no MISMATCHED_CPL_HASHES error here because it can't find the correct hash by ID (since the ID is wrong) */
444 check_verify_result_after_replace (
446 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
447 { dcp::VerificationNote::INVALID_XML }
451 /* Badly formatted <IssueDate> in CPL */
452 BOOST_AUTO_TEST_CASE (verify_test10)
454 check_verify_result_after_replace (
456 "<IssueDate>", "<IssueDate>x",
457 { dcp::VerificationNote::INVALID_XML,
458 dcp::VerificationNote::MISMATCHED_CPL_HASHES }
462 /* Badly-formatted <Id> in PKL */
463 BOOST_AUTO_TEST_CASE (verify_test11)
465 check_verify_result_after_replace (
467 "<Id>urn:uuid:2b9", "<Id>urn:uuid:xb9",
468 { dcp::VerificationNote::INVALID_XML }
472 /* Badly-formatted <Id> in ASSETMAP */
473 BOOST_AUTO_TEST_CASE (verify_test12)
475 check_verify_result_after_replace (
477 "<Id>urn:uuid:07e", "<Id>urn:uuid:x7e",
478 { dcp::VerificationNote::INVALID_XML }
482 /* Basic test of an Interop DCP */
483 BOOST_AUTO_TEST_CASE (verify_test13)
486 auto directories = setup (3, 13);
487 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
489 path const cpl_file = path("build") / "test" / "verify_test13" / "cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
490 path const pkl_file = path("build") / "test" / "verify_test13" / "pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
491 path const assetmap_file = "build/test/verify_test13/ASSETMAP";
493 auto st = stages.begin();
494 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
495 BOOST_REQUIRE (st->second);
496 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13"));
498 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
499 BOOST_REQUIRE (st->second);
500 BOOST_CHECK_EQUAL (st->second.get(), canonical(cpl_file));
502 BOOST_CHECK_EQUAL (st->first, "Checking reel");
503 BOOST_REQUIRE (!st->second);
505 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
506 BOOST_REQUIRE (st->second);
507 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
509 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
510 BOOST_REQUIRE (st->second);
511 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
513 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
514 BOOST_REQUIRE (st->second);
515 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
517 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
518 BOOST_REQUIRE (st->second);
519 BOOST_CHECK_EQUAL (st->second.get(), canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
521 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
522 BOOST_REQUIRE (st->second);
523 BOOST_CHECK_EQUAL (st->second.get(), canonical(pkl_file));
525 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
526 BOOST_REQUIRE (st->second);
527 BOOST_CHECK_EQUAL (st->second.get(), canonical(assetmap_file));
529 BOOST_REQUIRE (st == stages.end());
531 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
532 auto i = notes.begin ();
533 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
534 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INVALID_STANDARD);
537 /* DCP with a short asset */
538 BOOST_AUTO_TEST_CASE (verify_test14)
540 auto directories = setup (8, 14);
541 check_verify_result (
544 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
545 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
546 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("d7576dcb-a361-4139-96b8-267f5f8d7f91") },
547 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") },
548 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_INTRINSIC_DURATION, string("a2a87f5d-b749-4a7e-8d0c-9d48a4abf626") }
555 dcp_from_frame (dcp::ArrayData const& frame, path dir)
557 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
558 create_directories (dir);
559 auto writer = asset->start_write (dir / "pic.mxf", true);
560 for (int i = 0; i < 24; ++i) {
561 writer->write (frame.data(), frame.size());
565 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
566 return write_dcp_with_single_asset (dir, reel_asset);
570 /* DCP with an over-sized JPEG2000 frame */
571 BOOST_AUTO_TEST_CASE (verify_test15)
573 int const too_big = 1302083 * 2;
575 /* Compress a black image */
576 auto image = black_image ();
577 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
578 BOOST_REQUIRE (frame.size() < too_big);
580 /* Place it in a bigger block with some zero padding at the end */
581 dcp::ArrayData oversized_frame(too_big);
582 memcpy (oversized_frame.data(), frame.data(), frame.size());
583 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
585 path const dir("build/test/verify_test15");
586 prepare_directory (dir);
587 auto cpl = dcp_from_frame (oversized_frame, dir);
589 check_verify_result (
592 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
593 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
598 /* DCP with a nearly over-sized JPEG2000 frame */
599 BOOST_AUTO_TEST_CASE (verify_test16)
601 int const nearly_too_big = 1302083 * 0.98;
603 /* Compress a black image */
604 auto image = black_image ();
605 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
606 BOOST_REQUIRE (frame.size() < nearly_too_big);
608 /* Place it in a bigger block with some zero padding at the end */
609 dcp::ArrayData oversized_frame(nearly_too_big);
610 memcpy (oversized_frame.data(), frame.data(), frame.size());
611 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
613 path const dir("build/test/verify_test16");
614 prepare_directory (dir);
615 auto cpl = dcp_from_frame (oversized_frame, dir);
617 check_verify_result (
620 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(dir / "pic.mxf") },
621 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
626 /* DCP with a within-range JPEG2000 frame */
627 BOOST_AUTO_TEST_CASE (verify_test17)
629 /* Compress a black image */
630 auto image = black_image ();
631 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
632 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
634 path const dir("build/test/verify_test17");
635 prepare_directory (dir);
636 auto cpl = dcp_from_frame (frame, dir);
638 check_verify_result ({ dir }, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
642 /* DCP with valid Interop subtitles */
643 BOOST_AUTO_TEST_CASE (verify_test18)
645 path const dir("build/test/verify_test18");
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::INTEROP);
652 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD }});
656 /* DCP with broken Interop subtitles */
657 BOOST_AUTO_TEST_CASE (verify_test19)
659 using namespace boost::filesystem;
661 path const dir("build/test/verify_test19");
662 prepare_directory (dir);
663 copy_file ("test/data/subs1.xml", dir / "subs.xml");
664 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
665 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
666 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
669 Editor e (dir / "subs.xml");
670 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
673 check_verify_result (
676 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_STANDARD },
677 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 5 },
679 dcp::VerificationNote::VERIFY_ERROR,
680 dcp::VerificationNote::INVALID_XML,
681 string("element 'Foo' is not allowed for content model '(SubtitleID,MovieTitle,ReelNumber,Language,LoadFont*,Font*,Subtitle*)'"),
689 /* DCP with valid SMPTE subtitles */
690 BOOST_AUTO_TEST_CASE (verify_test20)
692 path const dir("build/test/verify_test20");
693 prepare_directory (dir);
694 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
695 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
696 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
697 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
699 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
703 /* DCP with broken SMPTE subtitles */
704 BOOST_AUTO_TEST_CASE (verify_test21)
706 using namespace boost::filesystem;
708 path const dir("build/test/verify_test21");
709 prepare_directory (dir);
710 copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
711 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
712 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
713 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
715 check_verify_result (
718 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'Foo'"), path(), 2 },
720 dcp::VerificationNote::VERIFY_ERROR,
721 dcp::VerificationNote::INVALID_XML,
722 string("element 'Foo' is not allowed for content model '(Id,ContentTitleText,AnnotationText?,IssueDate,ReelNumber?,Language?,EditRate,TimeCodeRate,StartTime?,DisplayType?,LoadFont*,SubtitleList)'"),
726 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
727 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
733 BOOST_AUTO_TEST_CASE (verify_test22)
735 path const ov_dir("build/test/verify_test22_ov");
736 prepare_directory (ov_dir);
738 auto image = black_image ();
739 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
740 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
741 dcp_from_frame (frame, ov_dir);
743 dcp::DCP ov (ov_dir);
746 path const vf_dir("build/test/verify_test22_vf");
747 prepare_directory (vf_dir);
749 auto picture = ov.cpls()[0]->reels()[0]->main_picture();
750 auto cpl = write_dcp_with_single_asset (vf_dir, picture);
752 check_verify_result (
755 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET, picture->asset()->id() },
756 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
761 /* DCP with valid CompositionMetadataAsset */
762 BOOST_AUTO_TEST_CASE (verify_test23)
764 path const dir("build/test/verify_test23");
765 prepare_directory (dir);
767 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
768 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
769 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
771 auto reel = make_shared<dcp::Reel>();
772 reel->add (reel_asset);
774 reel->add (simple_markers(16 * 24 - 1));
776 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
778 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
779 cpl->set_main_sound_sample_rate (48000);
780 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
781 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
787 dcp::String::compose("libdcp %1", dcp::version),
788 dcp::String::compose("libdcp %1", dcp::version),
789 dcp::LocalTime().as_string(),
793 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
797 path find_cpl (path dir)
799 for (auto i: directory_iterator(dir)) {
800 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
805 BOOST_REQUIRE (false);
810 /* DCP with invalid CompositionMetadataAsset */
811 BOOST_AUTO_TEST_CASE (verify_test24)
813 using namespace boost::filesystem;
815 path const dir("build/test/verify_test24");
816 prepare_directory (dir);
818 auto reel = make_shared<dcp::Reel>();
819 reel->add (black_picture_asset(dir));
820 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
822 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
823 cpl->set_main_sound_sample_rate (48000);
824 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
825 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
826 cpl->set_version_number (1);
828 reel->add (simple_markers());
834 dcp::String::compose("libdcp %1", dcp::version),
835 dcp::String::compose("libdcp %1", dcp::version),
836 dcp::LocalTime().as_string(),
841 Editor e (find_cpl("build/test/verify_test24"));
842 e.replace ("MainSound", "MainSoundX");
845 check_verify_result (
848 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXConfiguration'"), canonical(cpl->file().get()), 54 },
849 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:MainSoundXSampleRate'"), canonical(cpl->file().get()), 55 },
851 dcp::VerificationNote::VERIFY_ERROR,
852 dcp::VerificationNote::INVALID_XML,
853 string("element 'meta:MainSoundXConfiguration' is not allowed for content model "
854 "'(Id,AnnotationText?,EditRate,IntrinsicDuration,EntryPoint?,Duration?,"
855 "FullContentTitleText,ReleaseTerritory?,VersionNumber?,Chain?,Distributor?,"
856 "Facility?,AlternateContentVersionList?,Luminance?,MainSoundConfiguration,"
857 "MainSoundSampleRate,MainPictureStoredArea,MainPictureActiveArea,MainSubtitleLanguageList?,"
858 "ExtensionMetadataList?,)'"),
859 canonical(cpl->file().get()),
862 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) },
867 /* DCP with invalid CompositionMetadataAsset */
868 BOOST_AUTO_TEST_CASE (verify_test25)
870 path const dir("build/test/verify_test25");
871 prepare_directory (dir);
873 auto reel = make_shared<dcp::Reel>();
874 reel->add (black_picture_asset(dir));
875 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
877 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
878 cpl->set_main_sound_sample_rate (48000);
879 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
880 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
886 dcp::String::compose("libdcp %1", dcp::version),
887 dcp::String::compose("libdcp %1", dcp::version),
888 dcp::LocalTime().as_string(),
893 Editor e (find_cpl("build/test/verify_test25"));
894 e.replace ("meta:Width", "meta:WidthX");
897 check_verify_result (
899 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::FAILED_READ, string("missing XML tag Width in MainPictureStoredArea") }}
904 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
905 BOOST_AUTO_TEST_CASE (verify_test26)
907 path const dir("build/test/verify_test26");
908 prepare_directory (dir);
909 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
910 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
911 asset->_language = "wrong-andbad";
912 asset->write (dir / "subs.mxf");
913 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
914 reel_asset->_language = "badlang";
915 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
917 check_verify_result (
920 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
921 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
922 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
927 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
928 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
930 path const dir("build/test/verify_invalid_closed_caption_languages");
931 prepare_directory (dir);
932 copy_file ("test/data/subs.mxf", dir / "subs.mxf");
933 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
934 asset->_language = "wrong-andbad";
935 asset->write (dir / "subs.mxf");
936 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
937 reel_asset->_language = "badlang";
938 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
940 check_verify_result (
943 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("badlang") },
944 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("wrong-andbad") },
945 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
950 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
951 * the release territory.
953 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
955 path const dir("build/test/verify_various_invalid_languages");
956 prepare_directory (dir);
958 auto picture = simple_picture (dir, "foo");
959 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
960 auto reel = make_shared<dcp::Reel>();
961 reel->add (reel_picture);
962 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
963 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
964 reel->add (reel_sound);
965 reel->add (simple_markers());
967 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
969 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
970 cpl->_additional_subtitle_languages.push_back("andso-is-this");
971 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
972 cpl->set_main_sound_sample_rate (48000);
973 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
974 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
975 cpl->set_version_number (1);
976 cpl->_release_territory = "fred-jim";
977 auto dcp = make_shared<dcp::DCP>(dir);
981 dcp::String::compose("libdcp %1", dcp::version),
982 dcp::String::compose("libdcp %1", dcp::version),
983 dcp::LocalTime().as_string(),
987 check_verify_result (
990 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("this-is-wrong") },
991 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("andso-is-this") },
992 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("fred-jim") },
993 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_LANGUAGE, string("frobozz") },
999 vector<dcp::VerificationNote>
1000 check_picture_size (int width, int height, int frame_rate, bool three_d)
1002 using namespace boost::filesystem;
1004 path dcp_path = "build/test/verify_picture_test";
1005 prepare_directory (dcp_path);
1007 shared_ptr<dcp::PictureAsset> mp;
1009 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
1011 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
1013 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
1015 auto image = black_image (dcp::Size(width, height));
1016 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
1017 int const length = three_d ? frame_rate * 2 : frame_rate;
1018 for (int i = 0; i < length; ++i) {
1019 picture_writer->write (j2c.data(), j2c.size());
1021 picture_writer->finalize ();
1023 auto d = make_shared<dcp::DCP>(dcp_path);
1024 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1025 cpl->set_annotation_text ("A Test DCP");
1026 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
1027 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
1028 cpl->set_main_sound_sample_rate (48000);
1029 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
1030 cpl->set_main_picture_active_area (dcp::Size(1998, 1080));
1031 cpl->set_version_number (1);
1033 auto reel = make_shared<dcp::Reel>();
1036 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
1038 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
1041 reel->add (simple_markers(frame_rate));
1048 dcp::String::compose("libdcp %1", dcp::version),
1049 dcp::String::compose("libdcp %1", dcp::version),
1050 dcp::LocalTime().as_string(),
1054 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
1060 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
1062 auto notes = check_picture_size(width, height, frame_rate, three_d);
1064 BOOST_CHECK_EQUAL (notes.size(), 0U);
1070 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
1072 auto notes = check_picture_size(width, height, frame_rate, three_d);
1073 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1074 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1075 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_SIZE_IN_PIXELS);
1081 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
1083 auto notes = check_picture_size(width, height, frame_rate, three_d);
1084 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
1085 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1086 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_2K);
1092 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
1094 auto notes = check_picture_size(width, height, frame_rate, three_d);
1095 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1096 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1097 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE_FOR_4K);
1101 BOOST_AUTO_TEST_CASE (verify_picture_size)
1103 using namespace boost::filesystem;
1106 check_picture_size_ok (2048, 858, 24, false);
1107 check_picture_size_ok (2048, 858, 25, false);
1108 check_picture_size_ok (2048, 858, 48, false);
1109 check_picture_size_ok (2048, 858, 24, true);
1110 check_picture_size_ok (2048, 858, 25, true);
1111 check_picture_size_ok (2048, 858, 48, true);
1114 check_picture_size_ok (1998, 1080, 24, false);
1115 check_picture_size_ok (1998, 1080, 25, false);
1116 check_picture_size_ok (1998, 1080, 48, false);
1117 check_picture_size_ok (1998, 1080, 24, true);
1118 check_picture_size_ok (1998, 1080, 25, true);
1119 check_picture_size_ok (1998, 1080, 48, true);
1122 check_picture_size_ok (4096, 1716, 24, false);
1125 check_picture_size_ok (3996, 2160, 24, false);
1127 /* Bad frame size */
1128 check_picture_size_bad_frame_size (2050, 858, 24, false);
1129 check_picture_size_bad_frame_size (2048, 658, 25, false);
1130 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1131 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1133 /* Bad 2K frame rate */
1134 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1135 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1136 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1138 /* Bad 4K frame rate */
1139 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1140 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1143 auto notes = check_picture_size(3996, 2160, 24, true);
1144 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1145 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1146 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::INVALID_PICTURE_ASSET_RESOLUTION_FOR_3D);
1152 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1155 make_shared<dcp::SubtitleString>(
1163 dcp::Time(start_frame, 24, 24),
1164 dcp::Time(end_frame, 24, 24),
1180 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1182 path const dir("build/test/verify_closed_caption_xml_too_large");
1183 prepare_directory (dir);
1185 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1186 for (int i = 0; i < 2048; ++i) {
1187 add_test_subtitle (asset, i * 24, i * 24 + 20);
1189 asset->set_language (dcp::LanguageTag("de-DE"));
1190 asset->write (dir / "subs.mxf");
1191 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1192 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1194 check_verify_result (
1197 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1199 dcp::VerificationNote::VERIFY_BV21_ERROR,
1200 dcp::VerificationNote::INVALID_CLOSED_CAPTION_XML_SIZE_IN_BYTES,
1202 canonical(dir / "subs.mxf")
1204 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1205 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1211 shared_ptr<dcp::SMPTESubtitleAsset>
1212 make_large_subtitle_asset (path font_file)
1214 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1215 dcp::ArrayData big_fake_font(1024 * 1024);
1216 big_fake_font.write (font_file);
1217 for (int i = 0; i < 116; ++i) {
1218 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1226 verify_timed_text_asset_too_large (string name)
1228 auto const dir = path("build/test") / name;
1229 prepare_directory (dir);
1230 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1231 add_test_subtitle (asset, 0, 20);
1232 asset->set_language (dcp::LanguageTag("de-DE"));
1233 asset->write (dir / "subs.mxf");
1235 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1236 auto cpl = write_dcp_with_single_asset (dir, reel_asset);
1238 check_verify_result (
1241 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_SIZE_IN_BYTES, string("121696411"), canonical(dir / "subs.mxf") },
1242 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_TIMED_TEXT_FONT_SIZE_IN_BYTES, string("121634816"), canonical(dir / "subs.mxf") },
1243 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1244 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1245 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1250 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1252 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1253 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1257 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1259 path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1260 prepare_directory (dir);
1261 auto dcp = make_simple (dir, 1, 240);
1264 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1265 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1266 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1267 "<ContentTitleText>Content</ContentTitleText>"
1268 "<AnnotationText>Annotation</AnnotationText>"
1269 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1270 "<ReelNumber>1</ReelNumber>"
1271 "<EditRate>25 1</EditRate>"
1272 "<TimeCodeRate>25</TimeCodeRate>"
1273 "<StartTime>00:00:00:00</StartTime>"
1274 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1276 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1277 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1278 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1284 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1285 BOOST_REQUIRE (xml_file);
1286 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1288 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1289 subs->write (dir / "subs.mxf");
1291 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1292 dcp->cpls().front()->reels().front()->add(reel_subs);
1295 dcp::String::compose("libdcp %1", dcp::version),
1296 dcp::String::compose("libdcp %1", dcp::version),
1297 dcp::LocalTime().as_string(),
1301 check_verify_result (
1304 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE, canonical(dir / "subs.mxf") },
1305 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1310 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1312 path path ("build/test/verify_inconsistent_subtitle_languages");
1313 auto dcp = make_simple (path, 2, 240);
1314 auto cpl = dcp->cpls()[0];
1317 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1318 subs->set_language (dcp::LanguageTag("de-DE"));
1319 subs->add (simple_subtitle());
1320 subs->write (path / "subs1.mxf");
1321 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1322 cpl->reels()[0]->add(reel_subs);
1326 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1327 subs->set_language (dcp::LanguageTag("en-US"));
1328 subs->add (simple_subtitle());
1329 subs->write (path / "subs2.mxf");
1330 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1331 cpl->reels()[1]->add(reel_subs);
1336 dcp::String::compose("libdcp %1", dcp::version),
1337 dcp::String::compose("libdcp %1", dcp::version),
1338 dcp::LocalTime().as_string(),
1342 check_verify_result (
1345 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs1.mxf") },
1346 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_SUBTITLE_LANGUAGES },
1347 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(path / "subs2.mxf") }
1352 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1354 path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1355 prepare_directory (dir);
1356 auto dcp = make_simple (dir, 1, 240);
1359 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1360 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1361 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1362 "<ContentTitleText>Content</ContentTitleText>"
1363 "<AnnotationText>Annotation</AnnotationText>"
1364 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1365 "<ReelNumber>1</ReelNumber>"
1366 "<Language>de-DE</Language>"
1367 "<EditRate>25 1</EditRate>"
1368 "<TimeCodeRate>25</TimeCodeRate>"
1369 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1371 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1372 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1373 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1379 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1380 BOOST_REQUIRE (xml_file);
1381 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1383 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1384 subs->write (dir / "subs.mxf");
1386 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1387 dcp->cpls().front()->reels().front()->add(reel_subs);
1390 dcp::String::compose("libdcp %1", dcp::version),
1391 dcp::String::compose("libdcp %1", dcp::version),
1392 dcp::LocalTime().as_string(),
1396 check_verify_result (
1399 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1400 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1405 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1407 path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1408 prepare_directory (dir);
1409 auto dcp = make_simple (dir, 1, 240);
1412 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1413 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1414 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1415 "<ContentTitleText>Content</ContentTitleText>"
1416 "<AnnotationText>Annotation</AnnotationText>"
1417 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1418 "<ReelNumber>1</ReelNumber>"
1419 "<Language>de-DE</Language>"
1420 "<EditRate>25 1</EditRate>"
1421 "<TimeCodeRate>25</TimeCodeRate>"
1422 "<StartTime>00:00:02:00</StartTime>"
1423 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1425 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1426 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1427 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1433 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1434 BOOST_REQUIRE (xml_file);
1435 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1437 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1438 subs->write (dir / "subs.mxf");
1440 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1441 dcp->cpls().front()->reels().front()->add(reel_subs);
1444 dcp::String::compose("libdcp %1", dcp::version),
1445 dcp::String::compose("libdcp %1", dcp::version),
1446 dcp::LocalTime().as_string(),
1450 check_verify_result (
1453 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SUBTITLE_START_TIME, canonical(dir / "subs.mxf") },
1454 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME }
1462 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1465 , v_position(v_position_)
1477 shared_ptr<dcp::CPL>
1478 dcp_with_text (path dir, vector<TestText> subs)
1480 prepare_directory (dir);
1481 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1482 asset->set_start_time (dcp::Time());
1483 for (auto i: subs) {
1484 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1486 asset->set_language (dcp::LanguageTag("de-DE"));
1487 asset->write (dir / "subs.mxf");
1489 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1490 return write_dcp_with_single_asset (dir, reel_asset);
1494 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1496 auto const dir = path("build/test/verify_text_too_early");
1497 /* Just too early */
1498 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1499 check_verify_result (
1502 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_FIRST_TEXT_TIME },
1503 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1509 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1511 auto const dir = path("build/test/verify_text_not_too_early");
1512 /* Just late enough */
1513 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1514 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1518 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1520 auto const dir = path("build/test/verify_text_early_on_second_reel");
1521 prepare_directory (dir);
1523 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1524 asset1->set_start_time (dcp::Time());
1525 /* Just late enough */
1526 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1527 asset1->set_language (dcp::LanguageTag("de-DE"));
1528 asset1->write (dir / "subs1.mxf");
1529 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1530 auto reel1 = make_shared<dcp::Reel>();
1531 reel1->add (reel_asset1);
1532 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1533 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1534 reel1->add (markers1);
1536 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1537 asset2->set_start_time (dcp::Time());
1538 /* This would be too early on first reel but should be OK on the second */
1539 add_test_subtitle (asset2, 0, 4 * 24);
1540 asset2->set_language (dcp::LanguageTag("de-DE"));
1541 asset2->write (dir / "subs2.mxf");
1542 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1543 auto reel2 = make_shared<dcp::Reel>();
1544 reel2->add (reel_asset2);
1545 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 16 * 24, 0);
1546 markers2->set (dcp::Marker::LFOC, dcp::Time(16 * 24 - 1, 24, 24));
1547 reel2->add (markers2);
1549 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1552 auto dcp = make_shared<dcp::DCP>(dir);
1556 dcp::String::compose("libdcp %1", dcp::version),
1557 dcp::String::compose("libdcp %1", dcp::version),
1558 dcp::LocalTime().as_string(),
1563 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1567 BOOST_AUTO_TEST_CASE (verify_text_too_close)
1569 auto const dir = path("build/test/verify_text_too_close");
1570 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1574 { 5 * 24 + 1, 6 * 24 },
1576 check_verify_result (
1579 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_SPACING },
1580 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1585 BOOST_AUTO_TEST_CASE (verify_text_not_too_close)
1587 auto const dir = path("build/test/verify_text_not_too_close");
1588 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1592 { 5 * 24 + 16, 8 * 24 },
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_text_too_short)
1600 auto const dir = path("build/test/verify_text_too_short");
1601 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1602 check_verify_result (
1605 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_DURATION },
1606 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1611 BOOST_AUTO_TEST_CASE (verify_text_not_too_short)
1613 auto const dir = path("build/test/verify_text_not_too_short");
1614 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1615 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1619 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1)
1621 auto const dir = path ("build/test/verify_too_many_subtitle_lines1");
1622 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1625 { 96, 200, 0.0, "We" },
1626 { 96, 200, 0.1, "have" },
1627 { 96, 200, 0.2, "four" },
1628 { 96, 200, 0.3, "lines" }
1630 check_verify_result (
1633 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1634 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1639 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1)
1641 auto const dir = path ("build/test/verify_not_too_many_subtitle_lines1");
1642 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1645 { 96, 200, 0.0, "We" },
1646 { 96, 200, 0.1, "have" },
1647 { 96, 200, 0.2, "four" },
1649 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1653 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2)
1655 auto const dir = path ("build/test/verify_too_many_subtitle_lines2");
1656 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1659 { 96, 300, 0.0, "We" },
1660 { 96, 300, 0.1, "have" },
1661 { 150, 180, 0.2, "four" },
1662 { 150, 180, 0.3, "lines" }
1664 check_verify_result (
1667 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_COUNT },
1668 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1673 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2)
1675 auto const dir = path ("build/test/verify_not_too_many_subtitle_lines2");
1676 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1679 { 96, 300, 0.0, "We" },
1680 { 96, 300, 0.1, "have" },
1681 { 150, 180, 0.2, "four" },
1682 { 190, 250, 0.3, "lines" }
1684 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1688 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1)
1690 auto const dir = path ("build/test/verify_subtitle_lines_too_long1");
1691 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1694 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1696 check_verify_result (
1699 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::NEARLY_INVALID_SUBTITLE_LINE_LENGTH },
1700 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1705 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2)
1707 auto const dir = path ("build/test/verify_subtitle_lines_too_long2");
1708 auto cpl = dcp_with_text<dcp::ReelSubtitleAsset> (
1711 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1713 check_verify_result (
1716 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INVALID_SUBTITLE_LINE_LENGTH },
1717 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1722 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1)
1724 auto const dir = path ("build/test/verify_too_many_closed_caption_lines1");
1725 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1728 { 96, 200, 0.0, "We" },
1729 { 96, 200, 0.1, "have" },
1730 { 96, 200, 0.2, "four" },
1731 { 96, 200, 0.3, "lines" }
1733 check_verify_result (
1736 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1737 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1742 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1)
1744 auto const dir = path ("build/test/verify_not_too_many_closed_caption_lines1");
1745 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1748 { 96, 200, 0.0, "We" },
1749 { 96, 200, 0.1, "have" },
1750 { 96, 200, 0.2, "four" },
1752 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1756 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2)
1758 auto const dir = path ("build/test/verify_too_many_closed_caption_lines2");
1759 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1762 { 96, 300, 0.0, "We" },
1763 { 96, 300, 0.1, "have" },
1764 { 150, 180, 0.2, "four" },
1765 { 150, 180, 0.3, "lines" }
1767 check_verify_result (
1770 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_COUNT},
1771 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1776 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2)
1778 auto const dir = path ("build/test/verify_not_too_many_closed_caption_lines2");
1779 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1782 { 96, 300, 0.0, "We" },
1783 { 96, 300, 0.1, "have" },
1784 { 150, 180, 0.2, "four" },
1785 { 190, 250, 0.3, "lines" }
1787 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
1791 BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1)
1793 auto const dir = path ("build/test/verify_closed_caption_lines_too_long1");
1794 auto cpl = dcp_with_text<dcp::ReelClosedCaptionAsset> (
1797 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1799 check_verify_result (
1802 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_CLOSED_CAPTION_LINE_LENGTH },
1803 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
1808 BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k)
1810 path const dir("build/test/verify_sound_sampling_rate_must_be_48k");
1811 prepare_directory (dir);
1813 auto picture = simple_picture (dir, "foo");
1814 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1815 auto reel = make_shared<dcp::Reel>();
1816 reel->add (reel_picture);
1817 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1818 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1819 reel->add (reel_sound);
1820 reel->add (simple_markers());
1821 auto cpl = make_shared<dcp::CPL>("hello", dcp::TRAILER);
1823 auto dcp = make_shared<dcp::DCP>(dir);
1827 dcp::String::compose("libdcp %1", dcp::version),
1828 dcp::String::compose("libdcp %1", dcp::version),
1829 dcp::LocalTime().as_string(),
1833 check_verify_result (
1836 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE, string("96000"), canonical(dir / "audiofoo.mxf") },
1837 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() },
1842 BOOST_AUTO_TEST_CASE (verify_cpl_must_have_annotation_text)
1844 path const dir("build/test/verify_cpl_must_have_annotation_text");
1845 auto dcp = make_simple (dir);
1848 dcp::String::compose("libdcp %1", dcp::version),
1849 dcp::String::compose("libdcp %1", dcp::version),
1850 dcp::LocalTime().as_string(),
1854 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1856 auto const cpl = dcp->cpls()[0];
1859 BOOST_REQUIRE (cpl->file());
1860 Editor e(cpl->file().get());
1861 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "");
1864 check_verify_result (
1867 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1868 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1873 BOOST_AUTO_TEST_CASE (verify_cpl_annotation_text_should_be_same_as_content_title_text)
1875 path const dir("build/test/verify_cpl_annotation_text_should_be_same_as_content_title_text");
1876 auto dcp = make_simple (dir);
1879 dcp::String::compose("libdcp %1", dcp::version),
1880 dcp::String::compose("libdcp %1", dcp::version),
1881 dcp::LocalTime().as_string(),
1885 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
1886 auto const cpl = dcp->cpls()[0];
1889 BOOST_REQUIRE (cpl->file());
1890 Editor e(cpl->file().get());
1891 e.replace("<AnnotationText>A Test DCP</AnnotationText>", "<AnnotationText>A Test DCP 1</AnnotationText>");
1894 check_verify_result (
1897 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISMATCHED_CPL_ANNOTATION_TEXT, cpl->id(), canonical(cpl->file().get()) },
1898 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), canonical(cpl->file().get()) }
1903 BOOST_AUTO_TEST_CASE (verify_reel_assets_durations_must_match)
1905 path const dir("build/test/verify_reel_assets_durations_must_match");
1906 prepare_directory (dir);
1907 shared_ptr<dcp::DCP> dcp (new dcp::DCP(dir));
1908 shared_ptr<dcp::CPL> cpl (new dcp::CPL("A Test DCP", dcp::TRAILER));
1910 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (dir, "", 24);
1911 shared_ptr<dcp::SoundAsset> ms = simple_sound (dir, "", dcp::MXFMetadata(), "en-US", 25);
1913 auto reel = make_shared<dcp::Reel>(
1914 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
1915 make_shared<dcp::ReelSoundAsset>(ms, 0)
1918 reel->add (simple_markers());
1924 dcp::String::compose("libdcp %1", dcp::version),
1925 dcp::String::compose("libdcp %1", dcp::version),
1926 dcp::LocalTime().as_string(),
1930 check_verify_result (
1933 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_ASSET_DURATION },
1934 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), canonical(cpl->file().get()) }
1941 shared_ptr<dcp::CPL>
1942 verify_subtitles_must_be_in_all_reels_check (path dir, bool add_to_reel1, bool add_to_reel2)
1944 prepare_directory (dir);
1945 auto dcp = make_shared<dcp::DCP>(dir);
1946 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
1948 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1949 subs->set_language (dcp::LanguageTag("de-DE"));
1950 subs->set_start_time (dcp::Time());
1951 subs->add (simple_subtitle());
1952 subs->write (dir / "subs.mxf");
1953 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1955 auto reel1 = make_shared<dcp::Reel>(
1956 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1957 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1961 reel1->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1964 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1965 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
1966 reel1->add (markers1);
1970 auto reel2 = make_shared<dcp::Reel>(
1971 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
1972 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
1976 reel2->add (make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0));
1979 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
1980 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
1981 reel2->add (markers2);
1988 dcp::String::compose("libdcp %1", dcp::version),
1989 dcp::String::compose("libdcp %1", dcp::version),
1990 dcp::LocalTime().as_string(),
1998 BOOST_AUTO_TEST_CASE (verify_subtitles_must_be_in_all_reels)
2001 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2002 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, false);
2003 check_verify_result (
2006 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_MAIN_SUBTITLE_FROM_SOME_REELS },
2007 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2013 path dir ("build/test/verify_subtitles_must_be_in_all_reels2");
2014 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, true, true);
2015 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2019 path dir ("build/test/verify_subtitles_must_be_in_all_reels1");
2020 auto cpl = verify_subtitles_must_be_in_all_reels_check (dir, false, false);
2021 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2027 shared_ptr<dcp::CPL>
2028 verify_closed_captions_must_be_in_all_reels_check (path dir, int caps_in_reel1, int caps_in_reel2)
2030 prepare_directory (dir);
2031 auto dcp = make_shared<dcp::DCP>(dir);
2032 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2034 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2035 subs->set_language (dcp::LanguageTag("de-DE"));
2036 subs->set_start_time (dcp::Time());
2037 subs->add (simple_subtitle());
2038 subs->write (dir / "subs.mxf");
2040 auto reel1 = make_shared<dcp::Reel>(
2041 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2042 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2045 for (int i = 0; i < caps_in_reel1; ++i) {
2046 reel1->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2049 auto markers1 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2050 markers1->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
2051 reel1->add (markers1);
2055 auto reel2 = make_shared<dcp::Reel>(
2056 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2057 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2060 for (int i = 0; i < caps_in_reel2; ++i) {
2061 reel2->add (make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0));
2064 auto markers2 = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 240, 0);
2065 markers2->set (dcp::Marker::LFOC, dcp::Time(239, 24, 24));
2066 reel2->add (markers2);
2073 dcp::String::compose("libdcp %1", dcp::version),
2074 dcp::String::compose("libdcp %1", dcp::version),
2075 dcp::LocalTime().as_string(),
2083 BOOST_AUTO_TEST_CASE (verify_closed_captions_must_be_in_all_reels)
2086 path dir ("build/test/verify_closed_captions_must_be_in_all_reels1");
2087 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 3, 4);
2088 check_verify_result (
2091 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_CLOSED_CAPTION_ASSET_COUNTS },
2092 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2097 path dir ("build/test/verify_closed_captions_must_be_in_all_reels2");
2098 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 4, 4);
2099 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2103 path dir ("build/test/verify_closed_captions_must_be_in_all_reels3");
2104 auto cpl = verify_closed_captions_must_be_in_all_reels_check (dir, 0, 0);
2105 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }});
2112 verify_text_entry_point_check (path dir, dcp::VerificationNote::Code code, boost::function<void (shared_ptr<T>)> adjust)
2114 prepare_directory (dir);
2115 auto dcp = make_shared<dcp::DCP>(dir);
2116 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2118 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
2119 subs->set_language (dcp::LanguageTag("de-DE"));
2120 subs->set_start_time (dcp::Time());
2121 subs->add (simple_subtitle());
2122 subs->write (dir / "subs.mxf");
2123 auto reel_text = make_shared<T>(subs, dcp::Fraction(24, 1), 240, 0);
2126 auto reel = make_shared<dcp::Reel>(
2127 make_shared<dcp::ReelMonoPictureAsset>(simple_picture(dir, "", 240), 0),
2128 make_shared<dcp::ReelSoundAsset>(simple_sound(dir, "", dcp::MXFMetadata(), "en-US", 240), 0)
2131 reel->add (reel_text);
2133 reel->add (simple_markers(240));
2140 dcp::String::compose("libdcp %1", dcp::version),
2141 dcp::String::compose("libdcp %1", dcp::version),
2142 dcp::LocalTime().as_string(),
2146 check_verify_result (
2149 { dcp::VerificationNote::VERIFY_BV21_ERROR, code, subs->id() },
2150 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl->id(), cpl->file().get() }
2155 BOOST_AUTO_TEST_CASE (verify_text_entry_point)
2157 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2158 "build/test/verify_subtitle_entry_point_must_be_present",
2159 dcp::VerificationNote::MISSING_SUBTITLE_ENTRY_POINT,
2160 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2161 asset->unset_entry_point ();
2165 verify_text_entry_point_check<dcp::ReelSubtitleAsset> (
2166 "build/test/verify_subtitle_entry_point_must_be_zero",
2167 dcp::VerificationNote::INCORRECT_SUBTITLE_ENTRY_POINT,
2168 [](shared_ptr<dcp::ReelSubtitleAsset> asset) {
2169 asset->set_entry_point (4);
2173 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2174 "build/test/verify_closed_caption_entry_point_must_be_present",
2175 dcp::VerificationNote::MISSING_CLOSED_CAPTION_ENTRY_POINT,
2176 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2177 asset->unset_entry_point ();
2181 verify_text_entry_point_check<dcp::ReelClosedCaptionAsset> (
2182 "build/test/verify_closed_caption_entry_point_must_be_zero",
2183 dcp::VerificationNote::INCORRECT_CLOSED_CAPTION_ENTRY_POINT,
2184 [](shared_ptr<dcp::ReelClosedCaptionAsset> asset) {
2185 asset->set_entry_point (9);
2191 BOOST_AUTO_TEST_CASE (verify_assets_must_have_hashes)
2195 path const dir("build/test/verify_assets_must_have_hashes");
2196 auto dcp = make_simple (dir);
2199 dcp::String::compose("libdcp %1", dcp::version),
2200 dcp::String::compose("libdcp %1", dcp::version),
2201 dcp::LocalTime().as_string(),
2205 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
2206 auto const cpl = dcp->cpls()[0];
2209 BOOST_REQUIRE (cpl->file());
2210 Editor e(cpl->file().get());
2211 e.replace("<Hash>XGhFVrqZqapOJx5Fh2SLjj48Yjg=</Hash>", "");
2214 check_verify_result (
2217 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2218 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_HASH, string("1fab8bb0-cfaf-4225-ad6d-01768bc10470") }
2225 verify_markers_test (
2227 vector<pair<dcp::Marker, dcp::Time>> markers,
2228 vector<dcp::VerificationNote> test_notes
2231 auto dcp = make_simple (dir);
2232 dcp->cpls()[0]->set_content_kind (dcp::FEATURE);
2233 auto markers_asset = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), 24, 0);
2234 for (auto const& i: markers) {
2235 markers_asset->set (i.first, i.second);
2237 dcp->cpls()[0]->reels()[0]->add(markers_asset);
2240 dcp::String::compose("libdcp %1", dcp::version),
2241 dcp::String::compose("libdcp %1", dcp::version),
2242 dcp::LocalTime().as_string(),
2246 check_verify_result ({dir}, test_notes);
2250 BOOST_AUTO_TEST_CASE (verify_markers)
2252 verify_markers_test (
2253 "build/test/verify_markers_all_correct",
2255 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2256 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2257 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2258 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2263 verify_markers_test (
2264 "build/test/verify_markers_missing_ffec",
2266 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2267 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2268 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2271 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE }
2274 verify_markers_test (
2275 "build/test/verify_markers_missing_ffmc",
2277 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2278 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2279 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2282 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE }
2285 verify_markers_test (
2286 "build/test/verify_markers_missing_ffoc",
2288 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2289 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2290 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2293 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC}
2296 verify_markers_test (
2297 "build/test/verify_markers_missing_lfoc",
2299 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2300 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2301 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) }
2304 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC }
2307 verify_markers_test (
2308 "build/test/verify_markers_incorrect_ffoc",
2310 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2311 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2312 { dcp::Marker::FFOC, dcp::Time(3, 24, 24) },
2313 { dcp::Marker::LFOC, dcp::Time(23, 24, 24) }
2316 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_FFOC, string("3") }
2319 verify_markers_test (
2320 "build/test/verify_markers_incorrect_lfoc",
2322 { dcp::Marker::FFEC, dcp::Time(12, 24, 24) },
2323 { dcp::Marker::FFMC, dcp::Time(13, 24, 24) },
2324 { dcp::Marker::FFOC, dcp::Time(1, 24, 24) },
2325 { dcp::Marker::LFOC, dcp::Time(18, 24, 24) }
2328 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::INCORRECT_LFOC, string("18") }
2333 BOOST_AUTO_TEST_CASE (verify_cpl_metadata_version)
2335 path dir = "build/test/verify_cpl_metadata_version";
2336 prepare_directory (dir);
2337 auto dcp = make_simple (dir);
2338 auto cpl = dcp->cpls()[0];
2339 cpl->unset_version_number();
2342 dcp::String::compose("libdcp %1", dcp::version),
2343 dcp::String::compose("libdcp %1", dcp::version),
2344 dcp::LocalTime().as_string(),
2348 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA_VERSION_NUMBER, cpl->id(), cpl->file().get() }});
2352 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata1)
2354 path dir = "build/test/verify_cpl_extension_metadata1";
2355 auto dcp = make_simple (dir);
2358 dcp::String::compose("libdcp %1", dcp::version),
2359 dcp::String::compose("libdcp %1", dcp::version),
2360 dcp::LocalTime().as_string(),
2364 auto cpl = dcp->cpls()[0];
2367 Editor e (cpl->file().get());
2368 e.delete_lines ("<meta:ExtensionMetadataList>", "</meta:ExtensionMetadataList>");
2371 check_verify_result (
2374 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2375 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2380 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata2)
2382 path dir = "build/test/verify_cpl_extension_metadata2";
2383 auto dcp = make_simple (dir);
2386 dcp::String::compose("libdcp %1", dcp::version),
2387 dcp::String::compose("libdcp %1", dcp::version),
2388 dcp::LocalTime().as_string(),
2392 auto cpl = dcp->cpls()[0];
2395 Editor e (cpl->file().get());
2396 e.delete_lines ("<meta:ExtensionMetadata scope=\"http://isdcf.com/ns/cplmd/app\">", "</meta:ExtensionMetadata>");
2399 check_verify_result (
2402 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2403 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_EXTENSION_METADATA, cpl->id(), cpl->file().get() }
2408 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata3)
2410 path dir = "build/test/verify_cpl_extension_metadata3";
2411 auto dcp = make_simple (dir);
2414 dcp::String::compose("libdcp %1", dcp::version),
2415 dcp::String::compose("libdcp %1", dcp::version),
2416 dcp::LocalTime().as_string(),
2420 auto const cpl = dcp->cpls()[0];
2423 Editor e (cpl->file().get());
2424 e.replace ("<meta:Name>A", "<meta:NameX>A");
2425 e.replace ("n</meta:Name>", "n</meta:NameX>");
2428 check_verify_result (
2431 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:NameX'"), cpl->file().get(), 75 },
2432 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:NameX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2433 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2438 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata4)
2440 path dir = "build/test/verify_cpl_extension_metadata4";
2441 auto dcp = make_simple (dir);
2444 dcp::String::compose("libdcp %1", dcp::version),
2445 dcp::String::compose("libdcp %1", dcp::version),
2446 dcp::LocalTime().as_string(),
2450 auto cpl = dcp->cpls()[0];
2453 Editor e (cpl->file().get());
2454 e.replace ("Application", "Fred");
2457 check_verify_result (
2460 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2461 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> should be 'Application'"), cpl->file().get() },
2466 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata5)
2468 path dir = "build/test/verify_cpl_extension_metadata5";
2469 auto dcp = make_simple (dir);
2472 dcp::String::compose("libdcp %1", dcp::version),
2473 dcp::String::compose("libdcp %1", dcp::version),
2474 dcp::LocalTime().as_string(),
2478 auto cpl = dcp->cpls()[0];
2481 Editor e (cpl->file().get());
2482 e.replace ("DCP Constraints Profile", "Fred");
2485 check_verify_result (
2488 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2489 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Name> property should be 'DCP Constraints Profile'"), cpl->file().get() },
2494 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata6)
2496 path dir = "build/test/verify_cpl_extension_metadata6";
2497 auto dcp = make_simple (dir);
2500 dcp::String::compose("libdcp %1", dcp::version),
2501 dcp::String::compose("libdcp %1", dcp::version),
2502 dcp::LocalTime().as_string(),
2506 auto const cpl = dcp->cpls()[0];
2509 Editor e (cpl->file().get());
2510 e.replace ("<meta:Value>", "<meta:ValueX>");
2511 e.replace ("</meta:Value>", "</meta:ValueX>");
2514 check_verify_result (
2517 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:ValueX'"), cpl->file().get(), 79 },
2518 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:ValueX' is not allowed for content model '(Name,Value)'"), cpl->file().get(), 80 },
2519 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2524 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata7)
2526 path dir = "build/test/verify_cpl_extension_metadata7";
2527 auto dcp = make_simple (dir);
2530 dcp::String::compose("libdcp %1", dcp::version),
2531 dcp::String::compose("libdcp %1", dcp::version),
2532 dcp::LocalTime().as_string(),
2536 auto const cpl = dcp->cpls()[0];
2539 Editor e (cpl->file().get());
2540 e.replace ("SMPTE-RDD-52:2020-Bv2.1", "Fred");
2543 check_verify_result (
2546 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2547 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_EXTENSION_METADATA, string("<Value> property should be 'SMPTE-RDD-52:2020-Bv2.1'"), cpl->file().get() },
2552 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata8)
2554 path dir = "build/test/verify_cpl_extension_metadata8";
2555 auto dcp = make_simple (dir);
2558 dcp::String::compose("libdcp %1", dcp::version),
2559 dcp::String::compose("libdcp %1", dcp::version),
2560 dcp::LocalTime().as_string(),
2564 auto const cpl = dcp->cpls()[0];
2567 Editor e (cpl->file().get());
2568 e.replace ("<meta:Property>", "<meta:PropertyX>");
2569 e.replace ("</meta:Property>", "</meta:PropertyX>");
2572 check_verify_result (
2575 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyX'"), cpl->file().get(), 77 },
2576 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyX' is not allowed for content model '(Property+)'"), cpl->file().get(), 81 },
2577 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2582 BOOST_AUTO_TEST_CASE (verify_cpl_extension_metadata9)
2584 path dir = "build/test/verify_cpl_extension_metadata9";
2585 auto dcp = make_simple (dir);
2588 dcp::String::compose("libdcp %1", dcp::version),
2589 dcp::String::compose("libdcp %1", dcp::version),
2590 dcp::LocalTime().as_string(),
2594 auto const cpl = dcp->cpls()[0];
2597 Editor e (cpl->file().get());
2598 e.replace ("<meta:PropertyList>", "<meta:PropertyListX>");
2599 e.replace ("</meta:PropertyList>", "</meta:PropertyListX>");
2602 check_verify_result (
2605 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("no declaration found for element 'meta:PropertyListX'"), cpl->file().get(), 76 },
2606 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INVALID_XML, string("element 'meta:PropertyListX' is not allowed for content model '(Name,PropertyList?,)'"), cpl->file().get(), 82 },
2607 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl->id(), cpl->file().get() },
2613 BOOST_AUTO_TEST_CASE (verify_encrypted_cpl_is_signed)
2615 path dir = "build/test/verify_encrypted_cpl_is_signed";
2616 prepare_directory (dir);
2617 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2618 copy_file (i.path(), dir / i.path().filename());
2621 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2622 path const pkl = dir / ( "pkl_" + pkl_id + ".xml" );
2623 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2624 path const cpl = dir / ( "cpl_" + cpl_id + ".xml");
2628 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2631 check_verify_result (
2634 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISMATCHED_CPL_HASHES, cpl_id, canonical(cpl) },
2635 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl), },
2636 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2637 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2638 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2639 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2640 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2641 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_CPL_WITH_ENCRYPTED_CONTENT, cpl_id, canonical(cpl) }
2646 BOOST_AUTO_TEST_CASE (verify_encrypted_pkl_is_signed)
2648 path dir = "build/test/verify_encrypted_pkl_is_signed";
2649 prepare_directory (dir);
2650 for (auto i: directory_iterator("test/ref/DCP/encryption_test")) {
2651 copy_file (i.path(), dir / i.path().filename());
2654 string const cpl_id = "81fb54df-e1bf-4647-8788-ea7ba154375b";
2655 path const cpl = dir / ("cpl_" + cpl_id + ".xml");
2656 string const pkl_id = "93182bd2-b1e8-41a3-b5c8-6e6564273bff";
2657 path const pkl = dir / ("pkl_" + pkl_id + ".xml");
2660 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2663 check_verify_result (
2666 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL, pkl_id, canonical(pkl) },
2667 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFEC_IN_FEATURE },
2668 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_FFMC_IN_FEATURE },
2669 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_FFOC },
2670 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::MISSING_LFOC },
2671 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_CPL_METADATA, cpl_id, canonical(cpl) },
2672 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::UNSIGNED_PKL_WITH_ENCRYPTED_CONTENT, pkl_id, canonical(pkl) },
2677 BOOST_AUTO_TEST_CASE (verify_unencrypted_pkl_can_be_unsigned)
2679 path dir = "build/test/verify_unencrypted_pkl_can_be_unsigned";
2680 prepare_directory (dir);
2681 for (auto i: directory_iterator("test/ref/DCP/dcp_test1")) {
2682 copy_file (i.path(), dir / i.path().filename());
2686 Editor e (dir / "pkl_2b9b857f-ab4a-440e-a313-1ace0f1cfc95.xml");
2687 e.delete_lines ("<dsig:Signature", "</dsig:Signature>");
2690 check_verify_result ({dir}, {});
2694 BOOST_AUTO_TEST_CASE (verify_must_not_be_partially_encrypted)
2696 path dir ("build/test/verify_must_not_be_partially_encrypted");
2697 prepare_directory (dir);
2701 auto signer = make_shared<dcp::CertificateChain>();
2702 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/ca.self-signed.pem")));
2703 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/intermediate.signed.pem")));
2704 signer->add (dcp::Certificate(dcp::file_to_string("test/ref/crypt/leaf.signed.pem")));
2705 signer->set_key (dcp::file_to_string("test/ref/crypt/leaf.key"));
2707 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::TRAILER);
2711 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::SMPTE);
2714 auto writer = mp->start_write (dir / "video.mxf", false);
2715 dcp::ArrayData j2c ("test/data/flat_red.j2c");
2716 for (int i = 0; i < 24; ++i) {
2717 writer->write (j2c.data(), j2c.size());
2719 writer->finalize ();
2721 auto ms = simple_sound (dir, "", dcp::MXFMetadata(), "de-DE");
2723 auto reel = make_shared<dcp::Reel>(
2724 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
2725 make_shared<dcp::ReelSoundAsset>(ms, 0)
2728 reel->add (simple_markers());
2732 cpl->set_content_version (
2733 {"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"}
2735 cpl->set_annotation_text ("A Test DCP");
2736 cpl->set_issuer ("OpenDCP 0.0.25");
2737 cpl->set_creator ("OpenDCP 0.0.25");
2738 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
2739 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
2740 cpl->set_main_sound_sample_rate (48000);
2741 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
2742 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
2743 cpl->set_version_number (1);
2747 d.write_xml (dcp::SMPTE, "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP", signer);
2749 check_verify_result ({dir}, {{dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::PARTIALLY_ENCRYPTED}});