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 (list<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);
165 BOOST_REQUIRE_EQUAL (notes.size(), types_and_codes.size());
166 auto i = notes.begin();
167 auto j = types_and_codes.begin();
168 while (i != notes.end()) {
169 BOOST_CHECK_EQUAL (i->type(), j->first);
170 BOOST_CHECK_EQUAL (i->code(), j->second);
179 check_verify_result_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, vector<dcp::VerificationNote::Code> codes)
181 auto directories = setup (1, n);
185 e.replace (from, to);
188 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
190 BOOST_REQUIRE_EQUAL (notes.size(), codes.size());
191 auto i = notes.begin();
192 auto j = codes.begin();
193 while (i != notes.end()) {
194 BOOST_CHECK_EQUAL (i->code(), *j);
201 /* Check DCP as-is (should be OK) */
202 BOOST_AUTO_TEST_CASE (verify_test1)
205 auto directories = setup (1, 1);
206 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
208 boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
209 boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml";
210 boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
212 auto st = stages.begin();
213 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
214 BOOST_REQUIRE (st->second);
215 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
217 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
218 BOOST_REQUIRE (st->second);
219 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
221 BOOST_CHECK_EQUAL (st->first, "Checking reel");
222 BOOST_REQUIRE (!st->second);
224 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
225 BOOST_REQUIRE (st->second);
226 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
228 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
229 BOOST_REQUIRE (st->second);
230 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
232 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
233 BOOST_REQUIRE (st->second);
234 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
236 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
237 BOOST_REQUIRE (st->second);
238 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
240 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
241 BOOST_REQUIRE (st->second);
242 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
244 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
245 BOOST_REQUIRE (st->second);
246 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
248 BOOST_REQUIRE (st == stages.end());
250 BOOST_CHECK_EQUAL (notes.size(), 0);
253 /* Corrupt the MXFs and check that this is spotted */
254 BOOST_AUTO_TEST_CASE (verify_test2)
256 auto directories = setup (1, 2);
258 auto mod = fopen("build/test/verify_test2/video.mxf", "r+b");
260 fseek (mod, 4096, SEEK_SET);
262 fwrite (&x, sizeof(x), 1, mod);
265 mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
267 BOOST_REQUIRE_EQUAL (fseek(mod, -64, SEEK_END), 0);
268 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
271 dcp::ASDCPErrorSuspender sus;
272 check_verify_result (
275 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_HASH_INCORRECT },
276 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::SOUND_HASH_INCORRECT }
280 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
281 BOOST_AUTO_TEST_CASE (verify_test3)
283 auto directories = setup (1, 3);
286 Editor e ("build/test/verify_test3/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml");
287 e.replace ("<Hash>", "<Hash>x");
290 check_verify_result (
293 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT },
294 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DIFFER },
295 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DIFFER },
296 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
297 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
298 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
302 /* Corrupt the ContentKind in the CPL */
303 BOOST_AUTO_TEST_CASE (verify_test4)
305 auto directories = setup (1, 4);
308 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
309 e.replace ("<ContentKind>", "<ContentKind>x");
312 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
314 BOOST_REQUIRE_EQUAL (notes.size(), 1);
315 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
316 BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
320 boost::filesystem::path
323 return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
327 boost::filesystem::path
330 return dcp::String::compose("build/test/verify_test%1/pkl_cd49971e-bf4c-4594-8474-54ebef09a40c.xml", n);
334 boost::filesystem::path
337 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
342 BOOST_AUTO_TEST_CASE (verify_test5)
344 check_verify_result_after_replace (
346 "<FrameRate>24 1", "<FrameRate>99 1",
347 { dcp::VerificationNote::CPL_HASH_INCORRECT,
348 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE }
353 BOOST_AUTO_TEST_CASE (verify_test6)
355 auto directories = setup (1, 6);
357 boost::filesystem::remove ("build/test/verify_test6/video.mxf");
358 check_verify_result (directories, {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::MISSING_ASSET }});
362 boost::filesystem::path
365 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
368 /* Empty asset filename in ASSETMAP */
369 BOOST_AUTO_TEST_CASE (verify_test7)
371 check_verify_result_after_replace (
373 "<Path>video.mxf</Path>", "<Path></Path>",
374 { dcp::VerificationNote::EMPTY_ASSET_PATH }
378 /* Mismatched standard */
379 BOOST_AUTO_TEST_CASE (verify_test8)
381 check_verify_result_after_replace (
383 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
384 { dcp::VerificationNote::MISMATCHED_STANDARD,
385 dcp::VerificationNote::XML_VALIDATION_ERROR,
386 dcp::VerificationNote::CPL_HASH_INCORRECT }
390 /* Badly formatted <Id> in CPL */
391 BOOST_AUTO_TEST_CASE (verify_test9)
393 /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
394 check_verify_result_after_replace (
396 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
397 { dcp::VerificationNote::XML_VALIDATION_ERROR }
401 /* Badly formatted <IssueDate> in CPL */
402 BOOST_AUTO_TEST_CASE (verify_test10)
404 check_verify_result_after_replace (
406 "<IssueDate>", "<IssueDate>x",
407 { dcp::VerificationNote::XML_VALIDATION_ERROR,
408 dcp::VerificationNote::CPL_HASH_INCORRECT }
412 /* Badly-formatted <Id> in PKL */
413 BOOST_AUTO_TEST_CASE (verify_test11)
415 check_verify_result_after_replace (
417 "<Id>urn:uuid:cd4", "<Id>urn:uuid:xd4",
418 { dcp::VerificationNote::XML_VALIDATION_ERROR }
422 /* Badly-formatted <Id> in ASSETMAP */
423 BOOST_AUTO_TEST_CASE (verify_test12)
425 check_verify_result_after_replace (
427 "<Id>urn:uuid:63c", "<Id>urn:uuid:x3c",
428 { dcp::VerificationNote::XML_VALIDATION_ERROR }
432 /* Basic test of an Interop DCP */
433 BOOST_AUTO_TEST_CASE (verify_test13)
436 auto directories = setup (3, 13);
437 auto notes = dcp::verify (directories, &stage, &progress, xsd_test);
439 boost::filesystem::path const cpl_file = "build/test/verify_test13/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml";
440 boost::filesystem::path const pkl_file = "build/test/verify_test13/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml";
441 boost::filesystem::path const assetmap_file = "build/test/verify_test13/ASSETMAP";
443 auto st = stages.begin();
444 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
445 BOOST_REQUIRE (st->second);
446 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13"));
448 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
449 BOOST_REQUIRE (st->second);
450 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
452 BOOST_CHECK_EQUAL (st->first, "Checking reel");
453 BOOST_REQUIRE (!st->second);
455 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
456 BOOST_REQUIRE (st->second);
457 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
459 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
460 BOOST_REQUIRE (st->second);
461 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf"));
463 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
464 BOOST_REQUIRE (st->second);
465 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
467 BOOST_CHECK_EQUAL (st->first, "Checking sound asset metadata");
468 BOOST_REQUIRE (st->second);
469 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test13/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf"));
471 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
472 BOOST_REQUIRE (st->second);
473 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
475 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
476 BOOST_REQUIRE (st->second);
477 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
479 BOOST_REQUIRE (st == stages.end());
481 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
482 auto i = notes.begin ();
483 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
484 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::NOT_SMPTE);
487 /* DCP with a short asset */
488 BOOST_AUTO_TEST_CASE (verify_test14)
490 auto directories = setup (8, 14);
491 check_verify_result (
494 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
495 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
496 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL },
497 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::DURATION_TOO_SMALL },
498 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL }
505 dcp_from_frame (dcp::ArrayData const& frame, boost::filesystem::path dir)
507 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::SMPTE);
508 boost::filesystem::create_directories (dir);
509 auto writer = asset->start_write (dir / "pic.mxf", true);
510 for (int i = 0; i < 24; ++i) {
511 writer->write (frame.data(), frame.size());
515 auto reel_asset = make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
516 write_dcp_with_single_asset (dir, reel_asset);
520 /* DCP with an over-sized JPEG2000 frame */
521 BOOST_AUTO_TEST_CASE (verify_test15)
523 int const too_big = 1302083 * 2;
525 /* Compress a black image */
526 auto image = black_image ();
527 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
528 BOOST_REQUIRE (frame.size() < too_big);
530 /* Place it in a bigger block with some zero padding at the end */
531 dcp::ArrayData oversized_frame(too_big);
532 memcpy (oversized_frame.data(), frame.data(), frame.size());
533 memset (oversized_frame.data() + frame.size(), 0, too_big - frame.size());
535 boost::filesystem::path const dir("build/test/verify_test15");
536 boost::filesystem::remove_all (dir);
537 dcp_from_frame (oversized_frame, dir);
539 check_verify_result (
541 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE_IN_BYTES }}
546 /* DCP with a nearly over-sized JPEG2000 frame */
547 BOOST_AUTO_TEST_CASE (verify_test16)
549 int const nearly_too_big = 1302083 * 0.98;
551 /* Compress a black image */
552 auto image = black_image ();
553 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
554 BOOST_REQUIRE (frame.size() < nearly_too_big);
556 /* Place it in a bigger block with some zero padding at the end */
557 dcp::ArrayData oversized_frame(nearly_too_big);
558 memcpy (oversized_frame.data(), frame.data(), frame.size());
559 memset (oversized_frame.data() + frame.size(), 0, nearly_too_big - frame.size());
561 boost::filesystem::path const dir("build/test/verify_test16");
562 boost::filesystem::remove_all (dir);
563 dcp_from_frame (oversized_frame, dir);
565 check_verify_result (
567 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE_IN_BYTES }}
572 /* DCP with a within-range JPEG2000 frame */
573 BOOST_AUTO_TEST_CASE (verify_test17)
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() < 230000000 / (24 * 8));
580 boost::filesystem::path const dir("build/test/verify_test17");
581 boost::filesystem::remove_all (dir);
582 dcp_from_frame (frame, dir);
584 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
585 BOOST_REQUIRE_EQUAL (notes.size(), 0);
589 /* DCP with valid Interop subtitles */
590 BOOST_AUTO_TEST_CASE (verify_test18)
592 boost::filesystem::path const dir("build/test/verify_test18");
593 prepare_directory (dir);
594 boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
595 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
596 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
597 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
599 check_verify_result (
601 {{ dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE }}
606 /* DCP with broken Interop subtitles */
607 BOOST_AUTO_TEST_CASE (verify_test19)
609 boost::filesystem::path const dir("build/test/verify_test19");
610 prepare_directory (dir);
611 boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
612 auto asset = make_shared<dcp::InteropSubtitleAsset>(dir / "subs.xml");
613 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
614 write_dcp_with_single_asset (dir, reel_asset, dcp::INTEROP);
617 Editor e (dir / "subs.xml");
618 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
621 check_verify_result (
624 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::NOT_SMPTE },
625 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
626 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR }
631 /* DCP with valid SMPTE subtitles */
632 BOOST_AUTO_TEST_CASE (verify_test20)
634 boost::filesystem::path const dir("build/test/verify_test20");
635 prepare_directory (dir);
636 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
637 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
638 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
639 write_dcp_with_single_asset (dir, reel_asset);
641 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
643 BOOST_REQUIRE_EQUAL (notes.size(), 0);
647 /* DCP with broken SMPTE subtitles */
648 BOOST_AUTO_TEST_CASE (verify_test21)
650 boost::filesystem::path const dir("build/test/verify_test21");
651 prepare_directory (dir);
652 boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
653 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
654 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
655 write_dcp_with_single_asset (dir, reel_asset);
657 check_verify_result (
660 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
661 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
662 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
668 BOOST_AUTO_TEST_CASE (verify_test22)
670 boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
671 prepare_directory (ov_dir);
673 auto image = black_image ();
674 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
675 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
676 dcp_from_frame (frame, ov_dir);
678 dcp::DCP ov (ov_dir);
681 boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
682 prepare_directory (vf_dir);
684 write_dcp_with_single_asset (vf_dir, ov.cpls().front()->reels().front()->main_picture());
686 check_verify_result (
688 {{ dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::EXTERNAL_ASSET }});
692 /* DCP with valid CompositionMetadataAsset */
693 BOOST_AUTO_TEST_CASE (verify_test23)
695 boost::filesystem::path const dir("build/test/verify_test23");
696 prepare_directory (dir);
698 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
699 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
700 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
702 auto reel = make_shared<dcp::Reel>();
703 reel->add (reel_asset);
704 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
706 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
707 cpl->set_main_sound_sample_rate (48000);
708 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
709 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
713 dcp.write_xml (dcp::SMPTE);
715 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
716 BOOST_CHECK (notes.empty());
720 boost::filesystem::path find_cpl (boost::filesystem::path dir)
722 for (auto i: boost::filesystem::directory_iterator(dir)) {
723 if (boost::starts_with(i.path().filename().string(), "cpl_")) {
728 BOOST_REQUIRE (false);
733 /* DCP with invalid CompositionMetadataAsset */
734 BOOST_AUTO_TEST_CASE (verify_test24)
736 boost::filesystem::path const dir("build/test/verify_test24");
737 prepare_directory (dir);
739 auto reel = make_shared<dcp::Reel>();
740 reel->add (black_picture_asset(dir));
741 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
743 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
744 cpl->set_main_sound_sample_rate (48000);
745 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
746 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
750 dcp.write_xml (dcp::SMPTE);
753 Editor e (find_cpl("build/test/verify_test24"));
754 e.replace ("MainSound", "MainSoundX");
757 check_verify_result (
760 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
761 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
762 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::XML_VALIDATION_ERROR },
763 { dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::CPL_HASH_INCORRECT }
768 /* DCP with invalid CompositionMetadataAsset */
769 BOOST_AUTO_TEST_CASE (verify_test25)
771 boost::filesystem::path const dir("build/test/verify_test25");
772 prepare_directory (dir);
774 auto reel = make_shared<dcp::Reel>();
775 reel->add (black_picture_asset(dir));
776 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
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));
785 dcp.write_xml (dcp::SMPTE);
788 Editor e (find_cpl("build/test/verify_test25"));
789 e.replace ("meta:Width", "meta:WidthX");
792 check_verify_result (
794 {{ dcp::VerificationNote::VERIFY_ERROR, dcp::VerificationNote::GENERAL_READ }}
799 /* SMPTE DCP with invalid <Language> in the MainSubtitle reel and also in the XML within the MXF */
800 BOOST_AUTO_TEST_CASE (verify_test26)
802 boost::filesystem::path const dir("build/test/verify_test26");
803 prepare_directory (dir);
804 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
805 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
806 asset->_language = "wrong-andbad";
807 asset->write (dir / "subs.mxf");
808 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
809 reel_asset->_language = "badlang";
810 write_dcp_with_single_asset (dir, reel_asset);
812 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
813 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
814 auto i = notes.begin();
815 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
816 BOOST_REQUIRE (i->note());
817 BOOST_CHECK_EQUAL (*i->note(), "badlang");
819 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
820 BOOST_REQUIRE (i->note());
821 BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
825 /* SMPTE DCP with invalid <Language> in the MainClosedCaption reel and also in the XML within the MXF */
826 BOOST_AUTO_TEST_CASE (verify_invalid_closed_caption_languages)
828 boost::filesystem::path const dir("build/test/verify_invalid_closed_caption_languages");
829 prepare_directory (dir);
830 boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
831 auto asset = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.mxf");
832 asset->_language = "wrong-andbad";
833 asset->write (dir / "subs.mxf");
834 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
835 reel_asset->_language = "badlang";
836 write_dcp_with_single_asset (dir, reel_asset);
838 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
839 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
840 auto i = notes.begin ();
841 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
842 BOOST_REQUIRE (i->note());
843 BOOST_CHECK_EQUAL (*i->note(), "badlang");
845 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
846 BOOST_REQUIRE (i->note());
847 BOOST_CHECK_EQUAL (*i->note(), "wrong-andbad");
851 /* SMPTE DCP with invalid <Language> in the MainSound reel, the CPL additional subtitles languages and
852 * the release territory.
854 BOOST_AUTO_TEST_CASE (verify_various_invalid_languages)
856 boost::filesystem::path const dir("build/test/verify_various_invalid_languages");
857 prepare_directory (dir);
859 auto picture = simple_picture (dir, "foo");
860 auto reel_picture = make_shared<dcp::ReelMonoPictureAsset>(picture, 0);
861 auto reel = make_shared<dcp::Reel>();
862 reel->add (reel_picture);
863 auto sound = simple_sound (dir, "foo", dcp::MXFMetadata(), "frobozz");
864 auto reel_sound = make_shared<dcp::ReelSoundAsset>(sound, 0);
865 reel->add (reel_sound);
866 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
868 cpl->_additional_subtitle_languages.push_back("this-is-wrong");
869 cpl->_additional_subtitle_languages.push_back("andso-is-this");
870 cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
871 cpl->set_main_sound_sample_rate (48000);
872 cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
873 cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
874 cpl->_release_territory = "fred-jim";
875 auto dcp = make_shared<dcp::DCP>(dir);
877 dcp->write_xml (dcp::SMPTE);
879 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
881 BOOST_REQUIRE_EQUAL (notes.size(), 4U);
882 auto i = notes.begin ();
883 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
884 BOOST_REQUIRE (i->note());
885 BOOST_CHECK_EQUAL (*i->note(), "this-is-wrong");
887 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
888 BOOST_REQUIRE (i->note());
889 BOOST_CHECK_EQUAL (*i->note(), "andso-is-this");
891 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
892 BOOST_REQUIRE (i->note());
893 BOOST_CHECK_EQUAL (*i->note(), "fred-jim");
895 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::BAD_LANGUAGE);
896 BOOST_REQUIRE (i->note());
897 BOOST_CHECK_EQUAL (*i->note(), "frobozz");
903 list<dcp::VerificationNote>
904 check_picture_size (int width, int height, int frame_rate, bool three_d)
906 using namespace boost::filesystem;
908 path dcp_path = "build/test/verify_picture_test";
909 remove_all (dcp_path);
910 create_directories (dcp_path);
912 shared_ptr<dcp::PictureAsset> mp;
914 mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
916 mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(frame_rate, 1), dcp::SMPTE);
918 auto picture_writer = mp->start_write (dcp_path / "video.mxf", false);
920 auto image = black_image (dcp::Size(width, height));
921 auto j2c = dcp::compress_j2k (image, 100000000, frame_rate, three_d, width > 2048);
922 int const length = three_d ? frame_rate * 2 : frame_rate;
923 for (int i = 0; i < length; ++i) {
924 picture_writer->write (j2c.data(), j2c.size());
926 picture_writer->finalize ();
928 auto d = make_shared<dcp::DCP>(dcp_path);
929 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::FEATURE);
930 cpl->set_annotation_text ("A Test DCP");
931 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
933 auto reel = make_shared<dcp::Reel>();
936 reel->add (make_shared<dcp::ReelStereoPictureAsset>(std::dynamic_pointer_cast<dcp::StereoPictureAsset>(mp), 0));
938 reel->add (make_shared<dcp::ReelMonoPictureAsset>(std::dynamic_pointer_cast<dcp::MonoPictureAsset>(mp), 0));
944 d->write_xml (dcp::SMPTE);
946 return dcp::verify ({dcp_path}, &stage, &progress, xsd_test);
952 check_picture_size_ok (int width, int height, int frame_rate, bool three_d)
954 auto notes = check_picture_size(width, height, frame_rate, three_d);
956 BOOST_CHECK_EQUAL (notes.size(), 0U);
962 check_picture_size_bad_frame_size (int width, int height, int frame_rate, bool three_d)
964 auto notes = check_picture_size(width, height, frame_rate, three_d);
965 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
966 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
967 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_SIZE_IN_PIXELS);
973 check_picture_size_bad_2k_frame_rate (int width, int height, int frame_rate, bool three_d)
975 auto notes = check_picture_size(width, height, frame_rate, three_d);
976 BOOST_REQUIRE_EQUAL (notes.size(), 2U);
977 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
978 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_2K);
984 check_picture_size_bad_4k_frame_rate (int width, int height, int frame_rate, bool three_d)
986 auto notes = check_picture_size(width, height, frame_rate, three_d);
987 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
988 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
989 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_INVALID_FRAME_RATE_FOR_4K);
993 BOOST_AUTO_TEST_CASE (verify_picture_size)
995 using namespace boost::filesystem;
998 check_picture_size_ok (2048, 858, 24, false);
999 check_picture_size_ok (2048, 858, 25, false);
1000 check_picture_size_ok (2048, 858, 48, false);
1001 check_picture_size_ok (2048, 858, 24, true);
1002 check_picture_size_ok (2048, 858, 25, true);
1003 check_picture_size_ok (2048, 858, 48, true);
1006 check_picture_size_ok (1998, 1080, 24, false);
1007 check_picture_size_ok (1998, 1080, 25, false);
1008 check_picture_size_ok (1998, 1080, 48, false);
1009 check_picture_size_ok (1998, 1080, 24, true);
1010 check_picture_size_ok (1998, 1080, 25, true);
1011 check_picture_size_ok (1998, 1080, 48, true);
1014 check_picture_size_ok (4096, 1716, 24, false);
1017 check_picture_size_ok (3996, 2160, 24, false);
1019 /* Bad frame size */
1020 check_picture_size_bad_frame_size (2050, 858, 24, false);
1021 check_picture_size_bad_frame_size (2048, 658, 25, false);
1022 check_picture_size_bad_frame_size (1920, 1080, 48, true);
1023 check_picture_size_bad_frame_size (4000, 3000, 24, true);
1025 /* Bad 2K frame rate */
1026 check_picture_size_bad_2k_frame_rate (2048, 858, 26, false);
1027 check_picture_size_bad_2k_frame_rate (2048, 858, 31, false);
1028 check_picture_size_bad_2k_frame_rate (1998, 1080, 50, true);
1030 /* Bad 4K frame rate */
1031 check_picture_size_bad_4k_frame_rate (3996, 2160, 25, false);
1032 check_picture_size_bad_4k_frame_rate (3996, 2160, 48, false);
1035 auto notes = check_picture_size(3996, 2160, 24, true);
1036 BOOST_REQUIRE_EQUAL (notes.size(), 1U);
1037 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_BV21_ERROR);
1038 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_ASSET_4K_3D);
1044 add_test_subtitle (shared_ptr<dcp::SubtitleAsset> asset, int start_frame, int end_frame)
1047 make_shared<dcp::SubtitleString>(
1055 dcp::Time(start_frame, 24, 24),
1056 dcp::Time(end_frame, 24, 24),
1072 BOOST_AUTO_TEST_CASE (verify_closed_caption_xml_too_large)
1074 boost::filesystem::path const dir("build/test/verify_closed_caption_xml_too_large");
1075 prepare_directory (dir);
1077 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1078 for (int i = 0; i < 2048; ++i) {
1079 add_test_subtitle (asset, i * 24, i * 24 + 20);
1081 asset->set_language (dcp::LanguageTag("de-DE"));
1082 asset->write (dir / "subs.mxf");
1083 auto reel_asset = make_shared<dcp::ReelClosedCaptionAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1084 write_dcp_with_single_asset (dir, reel_asset);
1086 check_verify_result (
1089 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1090 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY },
1091 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::CLOSED_CAPTION_XML_TOO_LARGE_IN_BYTES }
1097 shared_ptr<dcp::SMPTESubtitleAsset>
1098 make_large_subtitle_asset (boost::filesystem::path font_file)
1100 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1101 dcp::ArrayData big_fake_font(1024 * 1024);
1102 big_fake_font.write (font_file);
1103 for (int i = 0; i < 116; ++i) {
1104 asset->add_font (dcp::String::compose("big%1", i), big_fake_font);
1112 verify_timed_text_asset_too_large (string name)
1114 auto const dir = boost::filesystem::path("build/test") / name;
1115 prepare_directory (dir);
1116 auto asset = make_large_subtitle_asset (dir / "font.ttf");
1117 add_test_subtitle (asset, 0, 20);
1118 asset->set_language (dcp::LanguageTag("de-DE"));
1119 asset->write (dir / "subs.mxf");
1121 auto reel_asset = make_shared<T>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1122 write_dcp_with_single_asset (dir, reel_asset);
1124 check_verify_result (
1127 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_ASSET_TOO_LARGE_IN_BYTES },
1128 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::TIMED_TEXT_FONTS_TOO_LARGE_IN_BYTES },
1129 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1130 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1135 BOOST_AUTO_TEST_CASE (verify_subtitle_asset_too_large)
1137 verify_timed_text_asset_too_large<dcp::ReelSubtitleAsset>("verify_subtitle_asset_too_large");
1138 verify_timed_text_asset_too_large<dcp::ReelClosedCaptionAsset>("verify_closed_caption_asset_too_large");
1142 BOOST_AUTO_TEST_CASE (verify_missing_language_tag_in_subtitle_xml)
1144 boost::filesystem::path dir = "build/test/verify_missing_language_tag_in_subtitle_xml";
1145 prepare_directory (dir);
1146 auto dcp = make_simple (dir, 1);
1149 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1150 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1151 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1152 "<ContentTitleText>Content</ContentTitleText>"
1153 "<AnnotationText>Annotation</AnnotationText>"
1154 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1155 "<ReelNumber>1</ReelNumber>"
1156 "<EditRate>25 1</EditRate>"
1157 "<TimeCodeRate>25</TimeCodeRate>"
1158 "<StartTime>00:00:00:00</StartTime>"
1159 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1161 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1162 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1163 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1169 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1170 BOOST_REQUIRE (xml_file);
1171 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1173 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1174 subs->write (dir / "subs.mxf");
1176 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1177 dcp->cpls().front()->reels().front()->add(reel_subs);
1178 dcp->write_xml (dcp::SMPTE);
1180 check_verify_result (
1183 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_LANGUAGE },
1184 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1189 BOOST_AUTO_TEST_CASE (verify_inconsistent_subtitle_languages)
1191 boost::filesystem::path path ("build/test/verify_inconsistent_subtitle_languages");
1192 auto dcp = make_simple (path, 2);
1193 auto cpl = dcp->cpls().front();
1196 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1197 subs->set_language (dcp::LanguageTag("de-DE"));
1198 subs->add (simple_subtitle());
1199 subs->write (path / "subs1.mxf");
1200 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1201 cpl->reels().front()->add(reel_subs);
1205 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
1206 subs->set_language (dcp::LanguageTag("en-US"));
1207 subs->add (simple_subtitle());
1208 subs->write (path / "subs2.mxf");
1209 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
1210 cpl->reels().back()->add(reel_subs);
1213 dcp->write_xml (dcp::SMPTE);
1215 check_verify_result (
1218 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1219 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_LANGUAGES_DIFFER },
1220 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME }
1225 BOOST_AUTO_TEST_CASE (verify_missing_start_time_tag_in_subtitle_xml)
1227 boost::filesystem::path dir = "build/test/verify_missing_start_time_tag_in_subtitle_xml";
1228 prepare_directory (dir);
1229 auto dcp = make_simple (dir, 1);
1232 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1233 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1234 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1235 "<ContentTitleText>Content</ContentTitleText>"
1236 "<AnnotationText>Annotation</AnnotationText>"
1237 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1238 "<ReelNumber>1</ReelNumber>"
1239 "<Language>de-DE</Language>"
1240 "<EditRate>25 1</EditRate>"
1241 "<TimeCodeRate>25</TimeCodeRate>"
1242 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1244 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1245 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1246 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1252 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1253 BOOST_REQUIRE (xml_file);
1254 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1256 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1257 subs->write (dir / "subs.mxf");
1259 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1260 dcp->cpls().front()->reels().front()->add(reel_subs);
1261 dcp->write_xml (dcp::SMPTE);
1263 check_verify_result (
1266 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::MISSING_SUBTITLE_START_TIME },
1267 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1272 BOOST_AUTO_TEST_CASE (verify_non_zero_start_time_tag_in_subtitle_xml)
1274 boost::filesystem::path dir = "build/test/verify_non_zero_start_time_tag_in_subtitle_xml";
1275 prepare_directory (dir);
1276 auto dcp = make_simple (dir, 1);
1279 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
1280 "<SubtitleReel xmlns=\"http://www.smpte-ra.org/schemas/428-7/2010/DCST\" xmlns:xs=\"http://www.w3.org/2001/schema\">"
1281 "<Id>urn:uuid:e6a8ae03-ebbf-41ed-9def-913a87d1493a</Id>"
1282 "<ContentTitleText>Content</ContentTitleText>"
1283 "<AnnotationText>Annotation</AnnotationText>"
1284 "<IssueDate>2018-10-02T12:25:14+02:00</IssueDate>"
1285 "<ReelNumber>1</ReelNumber>"
1286 "<Language>de-DE</Language>"
1287 "<EditRate>25 1</EditRate>"
1288 "<TimeCodeRate>25</TimeCodeRate>"
1289 "<StartTime>00:00:02:00</StartTime>"
1290 "<LoadFont ID=\"arial\">urn:uuid:e4f0ff0a-9eba-49e0-92ee-d89a88a575f6</LoadFont>"
1292 "<Font ID=\"arial\" Color=\"FFFEFEFE\" Weight=\"normal\" Size=\"42\" Effect=\"border\" EffectColor=\"FF181818\" AspectAdjust=\"1.00\">"
1293 "<Subtitle SpotNumber=\"1\" TimeIn=\"00:00:03:00\" TimeOut=\"00:00:04:10\" FadeUpTime=\"00:00:00:00\" FadeDownTime=\"00:00:00:00\">"
1294 "<Text Hposition=\"0.0\" Halign=\"center\" Valign=\"bottom\" Vposition=\"13.5\" Direction=\"ltr\">Hello world</Text>"
1300 auto xml_file = dcp::fopen_boost (dir / "subs.xml", "w");
1301 BOOST_REQUIRE (xml_file);
1302 fwrite (xml.c_str(), xml.size(), 1, xml_file);
1304 auto subs = make_shared<dcp::SMPTESubtitleAsset>(dir / "subs.xml");
1305 subs->write (dir / "subs.mxf");
1307 auto reel_subs = make_shared<dcp::ReelSubtitleAsset>(subs, dcp::Fraction(24, 1), 100, 0);
1308 dcp->cpls().front()->reels().front()->add(reel_subs);
1309 dcp->write_xml (dcp::SMPTE);
1311 check_verify_result (
1314 { dcp::VerificationNote::VERIFY_BV21_ERROR, dcp::VerificationNote::SUBTITLE_START_TIME_NON_ZERO },
1315 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1320 BOOST_AUTO_TEST_CASE (verify_text_too_early)
1322 auto const dir = boost::filesystem::path("build/test/verify_text_too_early");
1323 prepare_directory (dir);
1324 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1325 asset->set_start_time (dcp::Time());
1326 /* Just too early */
1327 add_test_subtitle (asset, 4 * 24 - 1, 5 * 24);
1328 asset->set_language (dcp::LanguageTag("de-DE"));
1329 asset->write (dir / "subs.mxf");
1331 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1332 write_dcp_with_single_asset (dir, reel_asset);
1334 check_verify_result (
1337 { dcp::VerificationNote::VERIFY_WARNING, dcp::VerificationNote::FIRST_TEXT_TOO_EARLY }
1342 BOOST_AUTO_TEST_CASE (verify_text_not_too_early)
1344 auto const dir = boost::filesystem::path("build/test/verify_text_not_too_early");
1345 prepare_directory (dir);
1346 auto asset = make_shared<dcp::SMPTESubtitleAsset>();
1347 asset->set_start_time (dcp::Time());
1348 /* Just late enough */
1349 add_test_subtitle (asset, 4 * 24, 5 * 24);
1350 asset->set_language (dcp::LanguageTag("de-DE"));
1351 asset->write (dir / "subs.mxf");
1353 auto reel_asset = make_shared<dcp::ReelSubtitleAsset>(asset, dcp::Fraction(24, 1), 16 * 24, 0);
1354 write_dcp_with_single_asset (dir, reel_asset);
1356 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1358 BOOST_REQUIRE (notes.empty());
1362 BOOST_AUTO_TEST_CASE (verify_text_early_on_second_reel)
1364 auto const dir = boost::filesystem::path("build/test/verify_text_early_on_second_reel");
1365 prepare_directory (dir);
1367 auto asset1 = make_shared<dcp::SMPTESubtitleAsset>();
1368 asset1->set_start_time (dcp::Time());
1369 /* Just late enough */
1370 add_test_subtitle (asset1, 4 * 24, 5 * 24);
1371 asset1->set_language (dcp::LanguageTag("de-DE"));
1372 asset1->write (dir / "subs.mxf");
1373 auto reel_asset1 = make_shared<dcp::ReelSubtitleAsset>(asset1, dcp::Fraction(24, 1), 16 * 24, 0);
1374 auto reel1 = make_shared<dcp::Reel>();
1375 reel1->add (reel_asset1);
1377 auto asset2 = make_shared<dcp::SMPTESubtitleAsset>();
1378 asset2->set_start_time (dcp::Time());
1379 /* This would be too early on first reel but should be OK on the second */
1380 add_test_subtitle (asset2, 0, 4 * 24);
1381 asset2->set_language (dcp::LanguageTag("de-DE"));
1382 asset2->write (dir / "subs.mxf");
1383 auto reel_asset2 = make_shared<dcp::ReelSubtitleAsset>(asset2, dcp::Fraction(24, 1), 16 * 24, 0);
1384 auto reel2 = make_shared<dcp::Reel>();
1385 reel2->add (reel_asset2);
1387 auto cpl = make_shared<dcp::CPL>("hello", dcp::FEATURE);
1390 auto dcp = make_shared<dcp::DCP>(dir);
1392 dcp->write_xml (dcp::SMPTE);
1394 auto notes = dcp::verify ({dir}, &stage, &progress, xsd_test);
1396 BOOST_REQUIRE (notes.empty());