2 Copyright (C) 2012-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.
34 #define BOOST_TEST_DYN_LINK
35 #define BOOST_TEST_MODULE libdcp_test
36 #include "compose.hpp"
39 #include "interop_subtitle_asset.h"
40 #include "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset.h"
43 #include "openjpeg_image.h"
44 #include "picture_asset_writer.h"
45 #include "picture_asset_writer.h"
47 #include "reel_asset.h"
48 #include "reel_interop_closed_caption_asset.h"
49 #include "reel_interop_subtitle_asset.h"
50 #include "reel_markers_asset.h"
51 #include "reel_mono_picture_asset.h"
52 #include "reel_mono_picture_asset.h"
53 #include "reel_smpte_closed_caption_asset.h"
54 #include "reel_smpte_subtitle_asset.h"
55 #include "reel_sound_asset.h"
56 #include "smpte_subtitle_asset.h"
57 #include "sound_asset.h"
58 #include "sound_asset_writer.h"
62 LIBDCP_DISABLE_WARNINGS
63 #include <asdcp/KM_util.h>
64 #include <asdcp/KM_prng.h>
65 LIBDCP_ENABLE_WARNINGS
67 LIBDCP_DISABLE_WARNINGS
68 #include <libxml++/libxml++.h>
69 LIBDCP_ENABLE_WARNINGS
70 #include <boost/test/unit_test.hpp>
78 using std::shared_ptr;
79 using std::make_shared;
80 using boost::optional;
83 boost::filesystem::path private_test;
84 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
92 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
93 private_test = boost::unit_test::framework::master_test_suite().argv[1];
96 using namespace boost::filesystem;
97 boost::system::error_code ec;
98 remove_all (xsd_test, ec);
99 boost::filesystem::create_directory (xsd_test);
100 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
101 copy_file (*i, xsd_test / i->path().filename());
108 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
110 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
111 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
113 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
117 auto whitespace_content = [](xmlpp::Node* node) {
118 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
119 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
122 auto ref_children = ref->get_children ();
123 auto test_children = test->get_children ();
125 auto k = ref_children.begin ();
126 auto l = test_children.begin ();
127 while (k != ref_children.end() && l != test_children.end()) {
129 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
134 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
139 if (whitespace_content(*k) && ignore_whitespace) {
144 if (whitespace_content(*l) && ignore_whitespace) {
149 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
151 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
152 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
153 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
154 if (ref_el && test_el) {
155 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
158 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
159 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
160 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
161 if (ref_cn && test_cn) {
162 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
169 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
173 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
177 BOOST_REQUIRE (k == ref_children.end());
178 BOOST_REQUIRE (l == test_children.end());
180 auto ref_attributes = ref->get_attributes ();
181 auto test_attributes = test->get_attributes ();
182 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
184 auto m = ref_attributes.begin();
185 auto n = test_attributes.begin();
186 while (m != ref_attributes.end ()) {
187 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
188 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
196 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
198 auto ref_parser = new xmlpp::DomParser ();
199 ref_parser->parse_memory (ref);
200 auto ref_root = ref_parser->get_document()->get_root_node ();
201 auto test_parser = new xmlpp::DomParser ();
202 test_parser->parse_memory (test);
203 auto test_root = test_parser->get_document()->get_root_node ();
205 check_xml (ref_root, test_root, ignore, ignore_whitespace);
209 check_file (boost::filesystem::path ref, boost::filesystem::path check)
211 uintmax_t size = boost::filesystem::file_size (ref);
212 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
213 auto ref_file = dcp::fopen_boost (ref, "rb");
214 BOOST_REQUIRE (ref_file);
215 auto check_file = dcp::fopen_boost (check, "rb");
216 BOOST_REQUIRE (check_file);
218 int const buffer_size = 65536;
219 auto ref_buffer = new uint8_t[buffer_size];
220 auto check_buffer = new uint8_t[buffer_size];
225 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
226 size_t r = fread (ref_buffer, 1, this_time, ref_file);
227 BOOST_CHECK_EQUAL (r, this_time);
228 r = fread (check_buffer, 1, this_time, check_file);
229 BOOST_CHECK_EQUAL (r, this_time);
231 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
232 for (int i = 0; i < buffer_size; ++i) {
233 if (ref_buffer[i] != check_buffer[i]) {
234 BOOST_CHECK_MESSAGE (
236 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
248 delete[] check_buffer;
255 RNGFixer::RNGFixer ()
257 Kumu::cth_test = true;
258 Kumu::FortunaRNG().Reset();
262 RNGFixer::~RNGFixer ()
264 Kumu::cth_test = false;
268 shared_ptr<dcp::MonoPictureAsset>
269 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
271 dcp::MXFMetadata mxf_meta;
272 mxf_meta.company_name = "OpenDCP";
273 mxf_meta.product_name = "OpenDCP";
274 mxf_meta.product_version = "0.0.25";
276 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
277 mp->set_metadata (mxf_meta);
281 auto picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
283 dcp::Size const size (1998, 1080);
284 auto image = make_shared<dcp::OpenJPEGImage>(size);
285 for (int i = 0; i < 3; ++i) {
286 memset (image->data(i), 0, 2 * size.width * size.height);
288 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
290 for (int i = 0; i < frames; ++i) {
291 picture_writer->write (j2c.data(), j2c.size());
293 picture_writer->finalize ();
299 shared_ptr<dcp::SoundAsset>
300 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate, optional<dcp::Key> key)
302 int const channels = 6;
304 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
305 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
309 ms->_language = language;
310 ms->set_metadata (mxf_meta);
311 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
313 int const samples_per_frame = sample_rate / 24;
315 float* silence[channels];
316 for (auto i = 0; i < channels; ++i) {
317 silence[i] = new float[samples_per_frame];
318 memset (silence[i], 0, samples_per_frame * sizeof(float));
321 for (auto i = 0; i < frames; ++i) {
322 sound_writer->write (silence, samples_per_frame);
325 sound_writer->finalize ();
327 for (auto i = 0; i < channels; ++i) {
336 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
338 /* Some known metadata */
339 dcp::MXFMetadata mxf_meta;
340 mxf_meta.company_name = "OpenDCP";
341 mxf_meta.product_name = "OpenDCP";
342 mxf_meta.product_version = "0.0.25";
344 auto constexpr sample_rate = 48000;
346 boost::filesystem::remove_all (path);
347 boost::filesystem::create_directories (path);
348 auto d = make_shared<dcp::DCP>(path);
349 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
350 cpl->set_annotation_text ("A Test DCP");
351 cpl->set_issuer ("OpenDCP 0.0.25");
352 cpl->set_creator ("OpenDCP 0.0.25");
353 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
354 cpl->set_content_version (
355 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
357 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
358 cpl->set_main_sound_sample_rate(sample_rate);
359 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
360 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
361 cpl->set_version_number(1);
363 for (int i = 0; i < reels; ++i) {
364 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
366 auto mp = simple_picture (path, suffix, frames, key);
367 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
369 auto reel = make_shared<dcp::Reel>(
370 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
371 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
374 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
376 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
378 if (i == reels - 1) {
379 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
391 shared_ptr<dcp::Subtitle>
394 return make_shared<dcp::SubtitleString>(
399 dcp::Colour(255, 255, 255),
402 dcp::Time(0, 0, 4, 0, 24),
403 dcp::Time(0, 0, 8, 0, 24),
411 dcp::Colour(255, 255, 255),
419 shared_ptr<dcp::ReelMarkersAsset>
420 simple_markers (int frames)
422 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
423 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
424 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
430 make_simple_with_interop_subs (boost::filesystem::path path)
432 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
434 auto subs = make_shared<dcp::InteropSubtitleAsset>();
435 subs->add (simple_subtitle());
437 boost::filesystem::create_directory (path / "subs");
438 dcp::ArrayData data(4096);
439 subs->add_font ("afont", data);
440 subs->write (path / "subs" / "subs.xml");
442 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
443 dcp->cpls().front()->reels().front()->add (reel_subs);
450 make_simple_with_smpte_subs (boost::filesystem::path path)
452 auto dcp = make_simple (path, 1, 192);
454 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
455 subs->set_language (dcp::LanguageTag("de-DE"));
456 subs->set_start_time (dcp::Time());
457 subs->add (simple_subtitle());
459 subs->write (path / "subs.mxf");
461 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
462 dcp->cpls().front()->reels().front()->add (reel_subs);
469 make_simple_with_interop_ccaps (boost::filesystem::path path)
471 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
473 auto subs = make_shared<dcp::InteropSubtitleAsset>();
474 subs->add (simple_subtitle());
475 subs->write (path / "ccap.xml");
477 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
478 dcp->cpls()[0]->reels()[0]->add (reel_caps);
485 make_simple_with_smpte_ccaps (boost::filesystem::path path)
487 auto dcp = make_simple (path, 1, 192);
489 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
490 subs->set_language (dcp::LanguageTag("de-DE"));
491 subs->set_start_time (dcp::Time());
492 subs->add (simple_subtitle());
493 subs->write (path / "ccap.mxf");
495 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
496 dcp->cpls()[0]->reels()[0]->add(reel_caps);
502 shared_ptr<dcp::OpenJPEGImage>
503 black_image (dcp::Size size)
505 auto image = make_shared<dcp::OpenJPEGImage>(size);
506 int const pixels = size.width * size.height;
507 for (int i = 0; i < 3; ++i) {
508 memset (image->data(i), 0, pixels * sizeof(int));
514 shared_ptr<dcp::ReelAsset>
515 black_picture_asset (boost::filesystem::path dir, int frames)
517 auto image = black_image ();
518 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
519 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
521 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
522 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
523 boost::filesystem::create_directories (dir);
524 auto writer = asset->start_write (dir / "pic.mxf", true);
525 for (int i = 0; i < frames; ++i) {
526 writer->write (frame.data(), frame.size());
530 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
534 boost::filesystem::path
535 find_file (boost::filesystem::path dir, string filename_part)
537 boost::optional<boost::filesystem::path> found;
538 for (auto i: boost::filesystem::directory_iterator(dir)) {
539 if (i.path().filename().string().find(filename_part) != string::npos) {
540 BOOST_REQUIRE (!found);
544 BOOST_REQUIRE (found);
549 BOOST_GLOBAL_FIXTURE (TestConfig);