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 "compose.hpp"
53 #include <boost/test/unit_test.hpp>
54 #include <boost/foreach.hpp>
55 #include <boost/algorithm/string.hpp>
64 using std::make_shared;
65 using boost::optional;
66 using std::shared_ptr;
69 static list<pair<string, optional<boost::filesystem::path> > > stages;
72 stage (string s, optional<boost::filesystem::path> p)
74 stages.push_back (make_pair (s, p));
84 prepare_directory (boost::filesystem::path path)
86 using namespace boost::filesystem;
88 create_directories (path);
92 static vector<boost::filesystem::path>
93 setup (int reference_number, int verify_test_number)
95 prepare_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
96 for (auto i: boost::filesystem::directory_iterator(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number))) {
97 boost::filesystem::copy_file (i.path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i.path().filename());
100 return { dcp::String::compose("build/test/verify_test%1", verify_test_number) };
107 write_dcp_with_single_asset (boost::filesystem::path dir, shared_ptr<dcp::ReelAsset> reel_asset, dcp::Standard standard = dcp::SMPTE)
109 auto reel = make_shared<dcp::Reel>();
110 reel->add (reel_asset);
111 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
113 auto dcp = make_shared<dcp::DCP>(dir);
115 dcp->write_xml (standard);
119 /** Class that can alter a file by searching and replacing strings within it.
120 * On destruction modifies the file whose name was given to the constructor.
125 Editor (boost::filesystem::path path)
128 _content = dcp::file_to_string (_path);
133 auto f = fopen(_path.string().c_str(), "w");
135 fwrite (_content.c_str(), _content.length(), 1, f);
139 void replace (string a, string b)
141 boost::algorithm::replace_all (_content, a, b);
145 boost::filesystem::path _path;
146 std::string _content;
152 dump_notes (vector<dcp::VerificationNote> const & notes)
154 for (auto i: notes) {
155 std::cout << dcp::note_to_string(i) << "\n";
162 check_verify_result (vector<boost::filesystem::path> dir, vector<std::pair<dcp::VerificationNote::Type, dcp::VerificationNote::Code>> types_and_codes)
164 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
166 BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size());
167 auto i = notes.begin();
168 auto j = types_and_codes.begin();
169 while (i != notes.end()) {
170 BOOST_CHECK_EQUAL (i->type(), j->first);
171 BOOST_CHECK_EQUAL (i->code(), j->second);
180 check_verify_result_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
182 auto directories = setup (1, n);
186 e.replace (from, to);
189 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
191 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
192 auto i = notes.begin();
193 auto j = codes.begin();
194 while (i != notes.end()) {
195 BOOST_CHECK_EQUAL (i->code(), *j);
202 /* Check DCP as-is (should be OK) */
203 BOOST_AUTO_TEST_CASE (verify_test1)
206 auto directories = setup (1, 1);
207 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
209 boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
210 boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml";
211 boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
213 auto st = stages.begin();
214 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
215 BOOST_REQUIRE (st->second);
216 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
218 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
219 BOOST_REQUIRE (st->second);
220 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
222 BOOST_CHECK_EQUAL (st->first, "Checking reel");
223 BOOST_REQUIRE (!st->second);
225 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
226 BOOST_REQUIRE (st->second);
227 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
229 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
230 BOOST_REQUIRE (st->second);
231 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
233 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
234 BOOST_REQUIRE (st->second);
235 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
237 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
238 BOOST_REQUIRE (st->second);
239 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
241 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
242 BOOST_REQUIRE (st->second);
243 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
245 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
246 BOOST_REQUIRE (st->second);
247 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
249 BOOST_REQUIRE (st == stages.end());
251 BOOST_CHECK_EQUAL (notes.size(), 0);
254 /* Corrupt the MXFs and check that this is spotted */
255 BOOST_AUTO_TEST_CASE (verify_test2)
257 auto directories = setup (1, 2);
259 auto mod = fopen("build/test/verify_test2/video.mxf", "r+b");
261 fseek (mod, 4096, SEEK_SET);
263 fwrite (&x, sizeof(x), 1, mod);
266 mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
268 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
269 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
272 dcp::ASDCPErrorSuspender sus;
273 check_verify_result (
276 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_HASH_INCORRECT },
277 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::SOUND_HASH_INCORRECT }
281 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
282 BOOST_AUTO_TEST_CASE (verify_test3)
284 auto directories = setup (1, 3);
287 Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml");
288 e.replace ("<Hash>", "<Hash>x");
291 check_verify_result (
294 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT },
295 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER },
296 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER },
297 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
298 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
299 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
303 /* Corrupt the ContentKind in the CPL */
304 BOOST_AUTO_TEST_CASE (verify_test4)
306 auto directories = setup (1, 4);
309 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
310 e.replace ("<ContentKind>", "<ContentKind>x");
313 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
315 BOOST_REQUIRE_EQUAL (notes.size(), 1);
316 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
317 BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
321 boost::filesystem::path
324 return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
328 boost::filesystem::path
331 return dcp::String::compose("build/test/verify_test%1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n);
335 boost::filesystem::path
338 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
343 BOOST_AUTO_TEST_CASE (verify_test5)
345 check_verify_result_after_replace (
347 "<FrameRate>24 1", "<FrameRate>99 1",
348 { dcp::VerificationNote::CPL_HASH_INCORRECT,
349 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
354 BOOST_AUTO_TEST_CASE (verify_test6)
356 auto directories = setup (1, 6);
358 boost::filesystem::remove ("build/test/verify_test6/video.mxf");
359 check_verify_result (directories, {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET }});
363 boost::filesystem::path
366 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
369 /* Empty asset filename in ASSETMAP */
370 BOOST_AUTO_TEST_CASE (verify_test7)
372 check_verify_result_after_replace (
374 "<Path>video.mxf</Path>", "<Path></Path>",
375 { dcp::VerificationNote::EMPTY_ASSET_PATH }
379 /* Mismatched standard */
380 BOOST_AUTO_TEST_CASE (verify_test8)
382 check_verify_result_after_replace (
384 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
385 { dcp::VerificationNote::MISMATCHED_STANDARD,
386 dcp::VerificationNote::XML_VALIDATION_ERROR,
387 dcp::VerificationNote::CPL_HASH_INCORRECT }
391 /* Badly formatted <Id> in CPL */
392 BOOST_AUTO_TEST_CASE (verify_test9)
394 /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
395 check_verify_result_after_replace (
397 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
398 { dcp::VerificationNote::XML_VALIDATION_ERROR }
402 /* Badly formatted <IssueDate> in CPL */
403 BOOST_AUTO_TEST_CASE (verify_test10)
405 check_verify_result_after_replace (
407 "<IssueDate>", "<IssueDate>x",
408 { dcp::VerificationNote::XML_VALIDATION_ERROR,
409 dcp::VerificationNote::CPL_HASH_INCORRECT }
413 /* Badly-formatted <Id> in PKL */
414 BOOST_AUTO_TEST_CASE (verify_test11)
416 check_verify_result_after_replace (
418 "<Id>urn:uuid:cd4", "<Id>urn:uuid:xd4",
419 { dcp::VerificationNote::XML_VALIDATION_ERROR }
423 /* Badly-formatted <Id> in ASSETMAP */
424 BOOST_AUTO_TEST_CASE (verify_test12)
426 check_verify_result_after_replace (
428 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
429 { dcp::VerificationNote::XML_VALIDATION_ERROR }
433 /* Basic test of an Interop DCP */
434 BOOST_AUTO_TEST_CASE (verify_test13)
437 auto directories = setup (3, 13);
438 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
440 boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
441 boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
442 boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
444 auto st = stages.begin();
445 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
446 BOOST_REQUIRE (st->second);
447 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
449 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
450 BOOST_REQUIRE (st->second);
451 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
453 BOOST_CHECK_EQUAL (st->first, "Checking reel");
454 BOOST_REQUIRE (!st->second);
456 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
457 BOOST_REQUIRE (st->second);
458 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
460 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
461 BOOST_REQUIRE (st->second);
462 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
464 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
465 BOOST_REQUIRE (st->second);
466 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
468 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
469 BOOST_REQUIRE (st->second);
470 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
472 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
473 BOOST_REQUIRE (st->second);
474 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
476 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
477 BOOST_REQUIRE (st->second);
478 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
480 BOOST_REQUIRE (st == stages.end());
482 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
483 auto i = notes.begin ();
484 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
485 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
488 /* DCP with a short asset */
489 BOOST_AUTO_TEST_CASE (verify_test14)
491 auto directories = setup (8, 14);
492 check_verify_result (
495 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
496 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
497 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL },
498 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
499 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL }
506 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
508 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
509 boost::filesystem::create_directories (dir);
510 auto writer = asset->start_write (dir / "pic.mxf", true);
511 for (int i = 0; i < 24; ++i) {
512 writer->write (frame.data(), frame.size());
516 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
517 write_dcp_with_single_asset (dir, reel_asset);
521 /* DCP with an over-sized JPEG2000 frame */
522 BOOST_AUTO_TEST_CASE (verify_test15)
524 int const too_big = 1302083 * 2;
526 /* Compress a black image */
527 auto image = black_image ();
528 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
529 BOOST_REQUIRE (frame.size() < too_big);
531 /* Place it in a bigger block with some zero padding at the end */
532 dcp::ArrayData oversized_frame(too_big);
533 memcpy (oversized_frame.data(), frame.data(), frame.size());
534 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
536 boost::filesystem::path const dir("build/test/verify_test15");
537 boost::filesystem::remove_all (dir);
538 dcp_from_frame (oversized_frame, dir);
540 check_verify_result (
542 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }}
547 /* DCP with a nearly over-sized JPEG2000 frame */
548 BOOST_AUTO_TEST_CASE (verify_test16)
550 int const nearly_too_big = 1302083 * 0.98;
552 /* Compress a black image */
553 auto image = black_image ();
554 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
555 BOOST_REQUIRE (frame.size() < nearly_too_big);
557 /* Place it in a bigger block with some zero padding at the end */
558 dcp::ArrayData oversized_frame(nearly_too_big);
559 memcpy (oversized_frame.data(), frame.data(), frame.size());
560 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
562 boost::filesystem::path const dir("build/test/verify_test16");
563 boost::filesystem::remove_all (dir);
564 dcp_from_frame (oversized_frame, dir);
566 check_verify_result (
568 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES }}
573 /* DCP with a within-range JPEG2000 frame */
574 BOOST_AUTO_TEST_CASE (verify_test17)
576 /* Compress a black image */
577 auto image = black_image ();
578 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
579 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
581 boost::filesystem::path const dir("build/test/verify_test17");
582 boost::filesystem::remove_all (dir);
583 dcp_from_frame (frame, dir);
585 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
586 BOOST_REQUIRE_EQUAL (notes.size(), 0);
590 /* DCP with valid Interop subtitles */
591 BOOST_AUTO_TEST_CASE (verify_test18)
593 boost::filesystem::path const dir("build/test/verify_test18");
594 prepare_directory (dir);
595 boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
596 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
597 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
598 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
600 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }});
604 /* DCP with broken Interop subtitles */
605 BOOST_AUTO_TEST_CASE (verify_test19)
607 boost::filesystem::path const dir("build/test/verify_test19");
608 prepare_directory (dir);
609 boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
610 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
611 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
612 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
615 Editor e (dir / "subs.xml");
616 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
619 check_verify_result (
622 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
623 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
624 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
629 /* DCP with valid SMPTE subtitles */
630 BOOST_AUTO_TEST_CASE (verify_test20)
632 boost::filesystem::path const dir("build/test/verify_test20");
633 prepare_directory (dir);
634 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
635 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
636 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
637 write_dcp_with_single_asset (dir, reel_asset);
639 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
640 BOOST_REQUIRE_EQUAL (notes.size(), 0);
644 /* DCP with broken SMPTE subtitles */
645 BOOST_AUTO_TEST_CASE (verify_test21)
647 boost::filesystem::path const dir("build/test/verify_test21");
648 prepare_directory (dir);
649 boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
650 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
651 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
652 write_dcp_with_single_asset (dir, reel_asset);
654 check_verify_result (
657 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
658 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
659 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
665 BOOST_AUTO_TEST_CASE (verify_test22)
667 boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
668 prepare_directory (ov_dir);
670 auto image = black_image ();
671 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
672 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
673 dcp_from_frame (frame, ov_dir);
675 dcp::DCP ov (ov_dir);
678 boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
679 prepare_directory (vf_dir);
681 write_dcp_with_single_asset (vf_dir, ov.cpls().front()->reels().front()->main_picture());
683 check_verify_result (
685 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET }});
689 /* DCP with valid CompositionMetadataAsset */
690 BOOST_AUTO_TEST_CASE (verify_test23)
692 boost::filesystem::path const dir("build/test/verify_test23");
693 prepare_directory (dir);
695 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
696 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
697 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
699 auto reel = make_shared<dcp::Reel>();
700 reel->add (reel_asset);
701 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
703 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
704 cpl->set_main_sound_sample_rate (48000);
705 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
706 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
710 dcp.write_xml (dcp::SMPTE);
712 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
713 BOOST_CHECK (notes.empty());
717 boost::filesystem::path find_cpl (boost::filesystem::path dir)
719 for (auto i: boost::filesystem::directory_iterator(dir)) {
720 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
725 BOOST_REQUIRE (false);
730 /* DCP with invalid CompositionMetadataAsset */
731 BOOST_AUTO_TEST_CASE (verify_test24)
733 boost::filesystem::path const dir("build/test/verify_test24");
734 prepare_directory (dir);
736 auto reel = make_shared<dcp::Reel>();
737 reel->add (black_picture_asset(dir));
738 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
740 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
741 cpl->set_main_sound_sample_rate (48000);
742 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
743 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
747 dcp.write_xml (dcp::SMPTE);
750 Editor e (find_cpl("build/test/verify_test24"));
751 e.replace ("MainSound", "MainSoundX");
754 check_verify_result (
757 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
758 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
759 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
760 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }
765 /* DCP with invalid CompositionMetadataAsset */
766 BOOST_AUTO_TEST_CASE (verify_test25)
768 boost::filesystem::path const dir("build/test/verify_test25");
769 prepare_directory (dir);
771 auto reel = make_shared<dcp::Reel>();
772 reel->add (black_picture_asset(dir));
773 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
775 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
776 cpl->set_main_sound_sample_rate (48000);
777 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
778 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
782 dcp.write_xml (dcp::SMPTE);
785 Editor e (find_cpl("build/test/verify_test25"));
786 e.replace ("meta:Width", "meta:WidthX");
789 check_verify_result (
791 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::GENERAL_READ }}
796 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
797 BOOST_AUTO_TEST_CASE (verify_test26)
799 boost::filesystem::path const dir("build/test/verify_test26");
800 prepare_directory (dir);
801 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
802 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
803 asset->_language = "wrong-andbad";
804 asset->write (dir / "subs.mxf");
805 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
806 reel_asset->_language = "badlang";
807 write_dcp_with_single_asset (dir, reel_asset);
809 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
810 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
811 auto i = notes.begin();
812 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
813 BOOST_REQUIRE (i->note());
814 BOOST_CHECK_EQUAL (*i->note(), "badlang");
816 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
817 BOOST_REQUIRE (i->note());
818 BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
822 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
823 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
825 boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages");
826 prepare_directory (dir);
827 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
828 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
829 asset->_language = "wrong-andbad";
830 asset->write (dir / "subs.mxf");
831 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
832 reel_asset->_language = "badlang";
833 write_dcp_with_single_asset (dir, reel_asset);
835 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
836 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
837 auto i = notes.begin ();
838 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
839 BOOST_REQUIRE (i->note());
840 BOOST_CHECK_EQUAL (*i->note(), "badlang");
842 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
843 BOOST_REQUIRE (i->note());
844 BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
848 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
849 * the release territory.
851 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
853 boost::filesystem::path const dir("build/test/verify_various_invalid_languages");
854 prepare_directory (dir);
856 auto picture = simple_picture (dir, "foo");
857 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
858 auto reel = make_shared<dcp::Reel>();
859 reel->add (reel_picture);
860 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
861 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
862 reel->add (reel_sound);
863 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
865 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
866 cpl->_additional_subtitle_languages.push_back("andso-is-this");
867 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
868 cpl->set_main_sound_sample_rate (48000);
869 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
870 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
871 cpl->_release_territory = "fred-jim";
872 auto dcp = make_shared<dcp::DCP>(dir);
874 dcp->write_xml (dcp::SMPTE);
876 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
877 BOOST_REQUIRE_EQUAL (notes.size(), 4U);
878 auto i = notes.begin ();
879 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
880 BOOST_REQUIRE (i->note());
881 BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
883 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
884 BOOST_REQUIRE (i->note());
885 BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
887 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
888 BOOST_REQUIRE (i->note());
889 BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
891 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
892 BOOST_REQUIRE (i->note());
893 BOOST_CHECK_EQUAL (*i->note(), "frobozz");
899 vector<dcp::VerificationNote>
900 check_picture_size (int width, int height, int frame_rate, bool three_d)
902 using namespace boost::filesystem;
904 path dcp_path = "build/test/verify_picture_test";
905 remove_all (dcp_path);
906 create_directories (dcp_path);
908 shared_ptr<dcp::PictureAsset> mp;
910 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
912 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
914 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
916 auto image = black_image (dcp::Size(width, height));
917 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
918 int const length = three_d ? frame_rate * 2 : frame_rate;
919 for (int i = 0; i < length; ++i) {
920 picture_writer->write (j2c.data(), j2c.size());
922 picture_writer->finalize ();
924 auto d = make_shared<dcp::DCP>(dcp_path);
925 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::FEATURE);
926 cpl->set_annotation_text ("A Test DCP");
927 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
929 auto reel = make_shared<dcp::Reel>();
932 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
934 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
940 d->write_xml (dcp::SMPTE);
942 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
948 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
950 auto notes = check_picture_size(width, height, frame_rate, three_d);
952 BOOST_CHECK_EQUAL (notes.size(), 0U);
958 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
960 auto notes = check_picture_size(width, height, frame_rate, three_d);
961 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
962 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
963 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
969 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
971 auto notes = check_picture_size(width, height, frame_rate, three_d);
972 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
973 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
974 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
980 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
982 auto notes = check_picture_size(width, height, frame_rate, three_d);
983 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
984 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
985 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
989 BOOST_AUTO_TEST_CASE (verify_picture_size)
991 using namespace boost::filesystem;
994 check_picture_size_ok (2048, 858, 24, false);
995 check_picture_size_ok (2048, 858, 25, false);
996 check_picture_size_ok (2048, 858, 48, false);
997 check_picture_size_ok (2048, 858, 24, true);
998 check_picture_size_ok (2048, 858, 25, true);
999 check_picture_size_ok (2048, 858, 48, true);
1002 check_picture_size_ok (1998, 1080, 24, false);
1003 check_picture_size_ok (1998, 1080, 25, false);
1004 check_picture_size_ok (1998, 1080, 48, false);
1005 check_picture_size_ok (1998, 1080, 24, true);
1006 check_picture_size_ok (1998, 1080, 25, true);
1007 check_picture_size_ok (1998, 1080, 48, true);
1010 check_picture_size_ok (4096, 1716, 24, false);
1013 check_picture_size_ok (3996, 2160, 24, false);
1015 /* Bad frame size */
1016 check_picture_size_bad_frame_size (2050, 858, 24, false);
1017 check_picture_size_bad_frame_size (2048, 658, 25, false);
1018 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1019 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1021 /* Bad 2K frame rate */
1022 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1023 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1024 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1026 /* Bad 4K frame rate */
1027 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1028 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1031 auto notes = check_picture_size(3996, 2160, 24, true);
1032 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1033 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1034 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1040 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame, float v_position = 0, string text = "Hello")
1043 make_shared<dcp::SubtitleString>(
1051 dcp::Time(start_frame, 24, 24),
1052 dcp::Time(end_frame, 24, 24),
1068 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1070 boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1071 prepare_directory (dir);
1073 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1074 for (int i = 0; i < 2048; ++i) {
1075 add_test_subtitle (asset, i * 24, i * 24 + 20);
1077 asset->set_language (dcp::LanguageTag("de-DE"));
1078 asset->write (dir / "subs.mxf");
1079 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1080 write_dcp_with_single_asset (dir, reel_asset);
1082 check_verify_result (
1085 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1086 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES },
1087 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1093 shared_ptr<dcp::SMPTESubtitleAsset>
1094 make_large_subtitle_asset (boost::filesystem::path font_file)
1096 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1097 dcp::ArrayData big_fake_font(1024 * 1024);
1098 big_fake_font.write (font_file);
1099 for (int i = 0; i < 116; ++i) {
1100 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1108 verify_timed_text_asset_too_large (string name)
1110 auto const dir = boost::filesystem::path("build/test") / name;
1111 prepare_directory (dir);
1112 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1113 add_test_subtitle (asset, 0, 20);
1114 asset->set_language (dcp::LanguageTag("de-DE"));
1115 asset->write (dir / "subs.mxf");
1117 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1118 write_dcp_with_single_asset (dir, reel_asset);
1120 check_verify_result (
1123 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES },
1124 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES },
1125 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1126 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1131 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1133 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1134 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1138 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1140 boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1141 prepare_directory (dir);
1142 auto dcp = make_simple (dir, 1);
1145 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1146 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1147 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1148 "<ContentTitleText>Content</ContentTitleText>"
1149 "<AnnotationText>Annotation</AnnotationText>"
1150 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1151 "<ReelNumber>1</ReelNumber>"
1152 "<EditRate>25 1</EditRate>"
1153 "<TimeCodeRate>25</TimeCodeRate>"
1154 "<StartTime>00:00:00:00</StartTime>"
1155 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1157 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1158 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1159 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1165 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1166 BOOST_REQUIRE (xml_file);
1167 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1169 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1170 subs->write (dir / "subs.mxf");
1172 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1173 dcp->cpls().front()->reels().front()->add(reel_subs);
1174 dcp->write_xml (dcp::SMPTE);
1176 check_verify_result (
1179 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE },
1180 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1185 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1187 boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1188 auto dcp = make_simple (path, 2, 240);
1189 auto cpl = dcp->cpls()[0];
1192 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1193 subs->set_language (dcp::LanguageTag("de-DE"));
1194 subs->add (simple_subtitle());
1195 subs->write (path / "subs1.mxf");
1196 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1197 cpl->reels()[0]->add(reel_subs);
1201 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1202 subs->set_language (dcp::LanguageTag("en-US"));
1203 subs->add (simple_subtitle());
1204 subs->write (path / "subs2.mxf");
1205 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1206 cpl->reels()[1]->add(reel_subs);
1209 dcp->write_xml (dcp::SMPTE);
1211 check_verify_result (
1214 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1215 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER },
1216 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
1221 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1223 boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1224 prepare_directory (dir);
1225 auto dcp = make_simple (dir, 1);
1228 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1229 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1230 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1231 "<ContentTitleText>Content</ContentTitleText>"
1232 "<AnnotationText>Annotation</AnnotationText>"
1233 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1234 "<ReelNumber>1</ReelNumber>"
1235 "<Language>de-DE</Language>"
1236 "<EditRate>25 1</EditRate>"
1237 "<TimeCodeRate>25</TimeCodeRate>"
1238 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1240 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1241 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1242 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1248 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1249 BOOST_REQUIRE (xml_file);
1250 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1252 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1253 subs->write (dir / "subs.mxf");
1255 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1256 dcp->cpls().front()->reels().front()->add(reel_subs);
1257 dcp->write_xml (dcp::SMPTE);
1259 check_verify_result (
1262 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1263 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1268 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1270 boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1271 prepare_directory (dir);
1272 auto dcp = make_simple (dir, 1);
1275 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1276 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1277 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1278 "<ContentTitleText>Content</ContentTitleText>"
1279 "<AnnotationText>Annotation</AnnotationText>"
1280 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1281 "<ReelNumber>1</ReelNumber>"
1282 "<Language>de-DE</Language>"
1283 "<EditRate>25 1</EditRate>"
1284 "<TimeCodeRate>25</TimeCodeRate>"
1285 "<StartTime>00:00:02:00</StartTime>"
1286 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1288 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1289 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1290 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1296 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1297 BOOST_REQUIRE (xml_file);
1298 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1300 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1301 subs->write (dir / "subs.mxf");
1303 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1304 dcp->cpls().front()->reels().front()->add(reel_subs);
1305 dcp->write_xml (dcp::SMPTE);
1307 check_verify_result (
1310 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO },
1311 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1319 TestText (int in_, int out_, float v_position_ = 0, string text_ = "Hello")
1322 , v_position(v_position_)
1335 dcp_with_text (boost::filesystem::path dir, vector<TestText> subs)
1337 prepare_directory (dir);
1338 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1339 asset->set_start_time (dcp::Time());
1340 for (auto i: subs) {
1341 add_test_subtitle (asset, i.in, i.out, i.v_position, i.text);
1343 asset->set_language (dcp::LanguageTag("de-DE"));
1344 asset->write (dir / "subs.mxf");
1346 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1347 write_dcp_with_single_asset (dir, reel_asset);
1351 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1353 auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
1354 /* Just too early */
1355 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24 - 1, 5 * 24 }});
1356 check_verify_result (
1358 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }});
1362 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1364 auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
1365 /* Just late enough */
1366 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 5 * 24 }});
1367 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1368 BOOST_REQUIRE (notes.empty());
1372 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1374 auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
1375 prepare_directory (dir);
1377 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1378 asset1->set_start_time (dcp::Time());
1379 /* Just late enough */
1380 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1381 asset1->set_language (dcp::LanguageTag("de-DE"));
1382 asset1->write (dir / "subs1.mxf");
1383 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1384 auto reel1 = make_shared<dcp::Reel>();
1385 reel1->add (reel_asset1);
1387 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1388 asset2->set_start_time (dcp::Time());
1389 /* This would be too early on first reel but should be OK on the second */
1390 add_test_subtitle (asset2, 0, 4 * 24);
1391 asset2->set_language (dcp::LanguageTag("de-DE"));
1392 asset2->write (dir / "subs2.mxf");
1393 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1394 auto reel2 = make_shared<dcp::Reel>();
1395 reel2->add (reel_asset2);
1397 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
1400 auto dcp = make_shared<dcp::DCP>(dir);
1402 dcp->write_xml (dcp::SMPTE);
1404 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1405 BOOST_REQUIRE (notes.empty());
1409 BOOST_AUTO_TEST_CASE (verify_text_too_close)
1411 auto const dir = boost::filesystem::path("build/test/verify_text_too_close");
1412 dcp_with_text<dcp::ReelSubtitleAsset> (
1416 { 5 * 24 + 1, 6 * 24 },
1418 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_CLOSE }});
1422 BOOST_AUTO_TEST_CASE (verify_text_not_too_close)
1424 auto const dir = boost::filesystem::path("build/test/verify_text_not_too_close");
1425 dcp_with_text<dcp::ReelSubtitleAsset> (
1429 { 5 * 24 + 16, 8 * 24 },
1431 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1432 BOOST_REQUIRE (notes.empty());
1436 BOOST_AUTO_TEST_CASE (verify_text_too_short)
1438 auto const dir = boost::filesystem::path("build/test/verify_text_too_short");
1439 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 1 }});
1440 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_TOO_SHORT }});
1444 BOOST_AUTO_TEST_CASE (verify_text_not_too_short)
1446 auto const dir = boost::filesystem::path("build/test/verify_text_not_too_short");
1447 dcp_with_text<dcp::ReelSubtitleAsset> (dir, {{ 4 * 24, 4 * 24 + 17 }});
1448 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1449 BOOST_REQUIRE (notes.empty());
1453 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines1)
1455 auto const dir = boost::filesystem::path ("verify_too_many_subtitle_lines1");
1456 dcp_with_text<dcp::ReelSubtitleAsset> (
1459 { 96, 200, 0.0, "We" },
1460 { 96, 200, 0.1, "have" },
1461 { 96, 200, 0.2, "four" },
1462 { 96, 200, 0.3, "lines" }
1464 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}});
1468 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines1)
1470 auto const dir = boost::filesystem::path ("verify_not_too_many_subtitle_lines1");
1471 dcp_with_text<dcp::ReelSubtitleAsset> (
1474 { 96, 200, 0.0, "We" },
1475 { 96, 200, 0.1, "have" },
1476 { 96, 200, 0.2, "four" },
1478 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1479 BOOST_REQUIRE (notes.empty());
1483 BOOST_AUTO_TEST_CASE (verify_too_many_subtitle_lines2)
1485 auto const dir = boost::filesystem::path ("verify_too_many_subtitle_lines2");
1486 dcp_with_text<dcp::ReelSubtitleAsset> (
1489 { 96, 300, 0.0, "We" },
1490 { 96, 300, 0.1, "have" },
1491 { 150, 180, 0.2, "four" },
1492 { 150, 180, 0.3, "lines" }
1494 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::TOO_MANY_SUBTITLE_LINES}});
1498 BOOST_AUTO_TEST_CASE (verify_not_too_many_subtitle_lines2)
1500 auto const dir = boost::filesystem::path ("verify_not_too_many_subtitle_lines2");
1501 dcp_with_text<dcp::ReelSubtitleAsset> (
1504 { 96, 300, 0.0, "We" },
1505 { 96, 300, 0.1, "have" },
1506 { 150, 180, 0.2, "four" },
1507 { 190, 250, 0.3, "lines" }
1509 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1510 BOOST_REQUIRE (notes.empty());
1514 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long1)
1516 auto const dir = boost::filesystem::path ("verify_subtitle_lines_too_long1");
1517 dcp_with_text<dcp::ReelSubtitleAsset> (
1520 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123" }
1522 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_LONGER_THAN_RECOMMENDED }});
1526 BOOST_AUTO_TEST_CASE (verify_subtitle_lines_too_long2)
1528 auto const dir = boost::filesystem::path ("verify_subtitle_lines_too_long2");
1529 dcp_with_text<dcp::ReelSubtitleAsset> (
1532 { 96, 300, 0.0, "012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
1534 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::SUBTITLE_LINE_TOO_LONG }});
1538 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines1)
1540 auto const dir = boost::filesystem::path ("verify_too_many_closed_caption_lines1");
1541 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1544 { 96, 200, 0.0, "We" },
1545 { 96, 200, 0.1, "have" },
1546 { 96, 200, 0.2, "four" },
1547 { 96, 200, 0.3, "lines" }
1549 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}});
1553 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines1)
1555 auto const dir = boost::filesystem::path ("verify_not_too_many_closed_caption_lines1");
1556 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1559 { 96, 200, 0.0, "We" },
1560 { 96, 200, 0.1, "have" },
1561 { 96, 200, 0.2, "four" },
1563 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1564 BOOST_REQUIRE (notes.empty());
1568 BOOST_AUTO_TEST_CASE (verify_too_many_closed_caption_lines2)
1570 auto const dir = boost::filesystem::path ("verify_too_many_closed_caption_lines2");
1571 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1574 { 96, 300, 0.0, "We" },
1575 { 96, 300, 0.1, "have" },
1576 { 150, 180, 0.2, "four" },
1577 { 150, 180, 0.3, "lines" }
1579 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TOO_MANY_CLOSED_CAPTION_LINES}});
1583 BOOST_AUTO_TEST_CASE (verify_not_too_many_closed_caption_lines2)
1585 auto const dir = boost::filesystem::path ("verify_not_too_many_closed_caption_lines2");
1586 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1589 { 96, 300, 0.0, "We" },
1590 { 96, 300, 0.1, "have" },
1591 { 150, 180, 0.2, "four" },
1592 { 190, 250, 0.3, "lines" }
1594 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1595 BOOST_REQUIRE (notes.empty());
1599 BOOST_AUTO_TEST_CASE (verify_closed_caption_lines_too_long1)
1601 auto const dir = boost::filesystem::path ("verify_closed_caption_lines_too_long1");
1602 dcp_with_text<dcp::ReelClosedCaptionAsset> (
1605 { 96, 300, 0.0, "0123456789012345678901234567890123" }
1607 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_LINE_TOO_LONG }});
1611 BOOST_AUTO_TEST_CASE (verify_sound_sampling_rate_must_be_48k)
1613 boost::filesystem::path const dir("verify_sound_sampling_rate_must_be_48k");
1614 prepare_directory (dir);
1616 auto picture = simple_picture (dir, "foo");
1617 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
1618 auto reel = make_shared<dcp::Reel>();
1619 reel->add (reel_picture);
1620 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "de-DE", 24, 96000);
1621 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
1622 reel->add (reel_sound);
1623 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
1625 auto dcp = make_shared<dcp::DCP>(dir);
1627 dcp->write_xml (dcp::SMPTE);
1629 check_verify_result ({dir}, {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::INVALID_SOUND_FRAME_RATE }});