2 Copyright (C) 2018-2020 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"
41 #include "openjpeg_image.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_asset_writer.h"
44 #include "compose.hpp"
45 #include <boost/test/unit_test.hpp>
46 #include <boost/foreach.hpp>
47 #include <boost/algorithm/string.hpp>
56 using boost::optional;
57 using boost::shared_ptr;
60 static list<pair<string, optional<boost::filesystem::path> > > stages;
61 static int next_verify_test_number = 1;
64 stage (string s, optional<boost::filesystem::path> p)
66 stages.push_back (make_pair (s, p));
75 static vector<boost::filesystem::path>
76 setup (int reference_number, int verify_test_number)
78 boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", verify_test_number));
79 boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
80 for (boost::filesystem::directory_iterator i(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number)); i != boost::filesystem::directory_iterator(); ++i) {
81 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i->path().filename());
84 vector<boost::filesystem::path> directories;
85 directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
91 /** Class that can alter a file by searching and replacing strings within it.
92 * On destruction modifies the file whose name was given to the constructor.
97 Editor (boost::filesystem::path path)
100 _content = dcp::file_to_string (_path);
105 FILE* f = fopen(_path.string().c_str(), "w");
107 fwrite (_content.c_str(), _content.length(), 1, f);
111 void replace (string a, string b)
113 boost::algorithm::replace_all (_content, a, b);
117 boost::filesystem::path _path;
118 std::string _content;
123 dump_notes (list<dcp::VerificationNote> const & notes)
125 BOOST_FOREACH (dcp::VerificationNote i, notes) {
126 std::cout << dcp::note_to_string(i) << "\n";
130 /* Check DCP as-is (should be OK) */
131 BOOST_AUTO_TEST_CASE (verify_test1)
134 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number);
135 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
137 boost::filesystem::path const cpl_file = dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", next_verify_test_number);
138 boost::filesystem::path const pkl_file = dcp::String::compose("build/test/verify_test1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", next_verify_test_number);
139 boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test1/ASSETMAP.xml", next_verify_test_number);
141 list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
142 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
143 BOOST_REQUIRE (st->second);
144 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
146 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
147 BOOST_REQUIRE (st->second);
148 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
150 BOOST_CHECK_EQUAL (st->first, "Checking reel");
151 BOOST_REQUIRE (!st->second);
153 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
154 BOOST_REQUIRE (st->second);
155 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
157 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
158 BOOST_REQUIRE (st->second);
159 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
161 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
162 BOOST_REQUIRE (st->second);
163 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/audio.mxf", next_verify_test_number)));
165 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
166 BOOST_REQUIRE (st->second);
167 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
169 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
170 BOOST_REQUIRE (st->second);
171 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
173 BOOST_REQUIRE (st == stages.end());
177 BOOST_CHECK_EQUAL (notes.size(), 0);
179 next_verify_test_number++;
182 /* Corrupt the MXFs and check that this is spotted */
183 BOOST_AUTO_TEST_CASE (verify_test2)
185 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
187 FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
189 fseek (mod, 4096, SEEK_SET);
191 fwrite (&x, sizeof(x), 1, mod);
194 mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
196 fseek (mod, 4096, SEEK_SET);
197 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
200 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
202 BOOST_REQUIRE_EQUAL (notes.size(), 2);
203 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
204 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
205 BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
206 BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
209 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
210 BOOST_AUTO_TEST_CASE (verify_test3)
212 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
215 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
216 e.replace ("<Hash>", "<Hash>x");
219 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
223 BOOST_REQUIRE_EQUAL (notes.size(), 6);
224 list<dcp::VerificationNote>::const_iterator i = notes.begin();
225 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
226 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
228 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
229 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
231 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
232 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
234 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
235 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
237 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
238 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
240 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
241 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
245 /* Corrupt the ContentKind in the CPL */
246 BOOST_AUTO_TEST_CASE (verify_test4)
248 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
251 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
252 e.replace ("<ContentKind>", "<ContentKind>x");
255 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
257 BOOST_REQUIRE_EQUAL (notes.size(), 1);
258 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
259 BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
263 boost::filesystem::path
266 return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
270 boost::filesystem::path
273 return dcp::String::compose("build/test/verify_test%1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", n);
277 boost::filesystem::path
280 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
284 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
286 vector<boost::filesystem::path> directories = setup (1, n);
290 e.replace (from, to);
293 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
297 BOOST_REQUIRE_EQUAL (notes.size(), 1);
298 BOOST_CHECK_EQUAL (notes.front().code(), code1);
302 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1, dcp::VerificationNote::Code code2)
304 vector<boost::filesystem::path> directories = setup (1, n);
308 e.replace (from, to);
311 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
315 BOOST_REQUIRE_EQUAL (notes.size(), 2);
316 BOOST_CHECK_EQUAL (notes.front().code(), code1);
317 BOOST_CHECK_EQUAL (notes.back().code(), code2);
321 void check_after_replace (
322 int n, boost::function<boost::filesystem::path (int)> file,
325 dcp::VerificationNote::Code code1,
326 dcp::VerificationNote::Code code2,
327 dcp::VerificationNote::Code code3
330 vector<boost::filesystem::path> directories = setup (1, n);
334 e.replace (from, to);
337 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
341 BOOST_REQUIRE_EQUAL (notes.size(), 3);
342 list<dcp::VerificationNote>::const_iterator i = notes.begin ();
343 BOOST_CHECK_EQUAL (i->code(), code1);
345 BOOST_CHECK_EQUAL (i->code(), code2);
347 BOOST_CHECK_EQUAL (i->code(), code3);
351 BOOST_AUTO_TEST_CASE (verify_test5)
353 check_after_replace (
354 next_verify_test_number++, &cpl,
355 "<FrameRate>24 1", "<FrameRate>99 1",
356 dcp::VerificationNote::CPL_HASH_INCORRECT,
357 dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
362 BOOST_AUTO_TEST_CASE (verify_test6)
364 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
366 boost::filesystem::remove ("build/test/verify_test6/video.mxf");
367 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
369 BOOST_REQUIRE_EQUAL (notes.size(), 1);
370 BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
371 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
375 boost::filesystem::path
378 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
381 /* Empty asset filename in ASSETMAP */
382 BOOST_AUTO_TEST_CASE (verify_test7)
384 check_after_replace (
385 next_verify_test_number++, &assetmap,
386 "<Path>video.mxf</Path>", "<Path></Path>",
387 dcp::VerificationNote::EMPTY_ASSET_PATH
391 /* Mismatched standard */
392 BOOST_AUTO_TEST_CASE (verify_test8)
394 check_after_replace (
395 next_verify_test_number++, &cpl,
396 "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
397 dcp::VerificationNote::MISMATCHED_STANDARD,
398 dcp::VerificationNote::XML_VALIDATION_ERROR,
399 dcp::VerificationNote::CPL_HASH_INCORRECT
403 /* Badly formatted <Id> in CPL */
404 BOOST_AUTO_TEST_CASE (verify_test9)
406 /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
407 check_after_replace (
408 next_verify_test_number++, &cpl,
409 "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
410 dcp::VerificationNote::XML_VALIDATION_ERROR
414 /* Badly formatted <IssueDate> in CPL */
415 BOOST_AUTO_TEST_CASE (verify_test10)
417 check_after_replace (
418 next_verify_test_number++, &cpl,
419 "<IssueDate>", "<IssueDate>x",
420 dcp::VerificationNote::XML_VALIDATION_ERROR,
421 dcp::VerificationNote::CPL_HASH_INCORRECT
425 /* Badly-formatted <Id> in PKL */
426 BOOST_AUTO_TEST_CASE (verify_test11)
428 check_after_replace (
429 next_verify_test_number++, &pkl,
430 "<Id>urn:uuid:ae8", "<Id>urn:uuid:xe8",
431 dcp::VerificationNote::XML_VALIDATION_ERROR
435 /* Badly-formatted <Id> in ASSETMAP */
436 BOOST_AUTO_TEST_CASE (verify_test12)
438 check_after_replace (
439 next_verify_test_number++, &asset_map,
440 "<Id>urn:uuid:74e", "<Id>urn:uuid:x4e",
441 dcp::VerificationNote::XML_VALIDATION_ERROR
445 /* Basic test of an Interop DCP */
446 BOOST_AUTO_TEST_CASE (verify_test13)
449 vector<boost::filesystem::path> directories = setup (3, next_verify_test_number);
450 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
452 boost::filesystem::path const cpl_file = dcp::String::compose("build/test/verify_test%1/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml", next_verify_test_number);
453 boost::filesystem::path const pkl_file = dcp::String::compose("build/test/verify_test%1/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml", next_verify_test_number);
454 boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test%1/ASSETMAP", next_verify_test_number);
456 list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
457 BOOST_CHECK_EQUAL (st->first, "Checking DCP");
458 BOOST_REQUIRE (st->second);
459 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
461 BOOST_CHECK_EQUAL (st->first, "Checking CPL");
462 BOOST_REQUIRE (st->second);
463 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
465 BOOST_CHECK_EQUAL (st->first, "Checking reel");
466 BOOST_REQUIRE (!st->second);
468 BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
469 BOOST_REQUIRE (st->second);
470 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number)));
472 BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
473 BOOST_REQUIRE (st->second);
474 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number)));
476 BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
477 BOOST_REQUIRE (st->second);
478 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf", next_verify_test_number)));
480 BOOST_CHECK_EQUAL (st->first, "Checking PKL");
481 BOOST_REQUIRE (st->second);
482 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
484 BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
485 BOOST_REQUIRE (st->second);
486 BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
488 BOOST_REQUIRE (st == stages.end());
492 BOOST_CHECK_EQUAL (notes.size(), 0);
494 next_verify_test_number++;
497 /* DCP with a short asset */
498 BOOST_AUTO_TEST_CASE (verify_test14)
500 vector<boost::filesystem::path> directories = setup (8, next_verify_test_number);
501 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
505 BOOST_REQUIRE_EQUAL (notes.size(), 4);
506 list<dcp::VerificationNote>::const_iterator i = notes.begin ();
507 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
509 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
511 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
513 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
515 next_verify_test_number++;
520 shared_ptr<dcp::OpenJPEGImage>
523 shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
524 int const pixels = 1998 * 1080;
525 for (int i = 0; i < 3; ++i) {
526 int* p = image->data(i);
527 for (int j = 0; j < pixels; ++j) {
537 dcp_from_frame (dcp::Data const& frame, boost::filesystem::path dir)
539 shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
540 boost::filesystem::create_directories (dir);
541 shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
542 for (int i = 0; i < 24; ++i) {
543 writer->write (frame.data().get(), frame.size());
547 shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelMonoPictureAsset(asset, 0));
548 shared_ptr<dcp::Reel> reel(new dcp::Reel());
549 reel->add (reel_asset);
550 shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
552 shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
554 dcp->write_xml (dcp::SMPTE);
558 /* DCP with an over-sized JPEG2000 frame */
559 BOOST_AUTO_TEST_CASE (verify_test15)
561 /* Compress a random image with a bandwidth of 500Mbit/s */
562 shared_ptr<dcp::OpenJPEGImage> image = random_image ();
563 dcp::Data frame = dcp::compress_j2k (image, 500000000, 24, false, false);
565 boost::filesystem::path const dir("build/test/verify_test15");
566 dcp_from_frame (frame, dir);
568 vector<boost::filesystem::path> dirs;
569 dirs.push_back (dir);
570 list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
571 BOOST_REQUIRE_EQUAL (notes.size(), 1);
572 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE);
576 /* DCP with a nearly over-sized JPEG2000 frame */
577 BOOST_AUTO_TEST_CASE (verify_test16)
579 /* Compress a random image with a bandwidth of 500Mbit/s */
580 shared_ptr<dcp::OpenJPEGImage> image = random_image ();
581 dcp::Data frame = dcp::compress_j2k (image, 240000000, 24, false, false);
583 boost::filesystem::path const dir("build/test/verify_test16");
584 dcp_from_frame (frame, dir);
586 vector<boost::filesystem::path> dirs;
587 dirs.push_back (dir);
588 list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
589 BOOST_REQUIRE_EQUAL (notes.size(), 1);
590 BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE);
594 /* DCP with a within-range JPEG2000 frame */
595 BOOST_AUTO_TEST_CASE (verify_test17)
597 /* Compress a random image with a bandwidth of 500Mbit/s */
598 shared_ptr<dcp::OpenJPEGImage> image = random_image ();
599 dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
601 boost::filesystem::path const dir("build/test/verify_test17");
602 dcp_from_frame (frame, dir);
604 vector<boost::filesystem::path> dirs;
605 dirs.push_back (dir);
606 list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, "xsd");
607 BOOST_REQUIRE_EQUAL (notes.size(), 0);