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"
40 #include "interop_subtitle_asset.h"
41 #include "mono_picture_asset.h"
42 #include "picture_asset_writer.h"
44 #include "reel_mono_picture_asset.h"
45 #include "reel_sound_asset.h"
46 #include "reel_closed_caption_asset.h"
47 #include "reel_subtitle_asset.h"
48 #include "sound_asset.h"
49 #include "sound_asset_writer.h"
50 #include "smpte_subtitle_asset.h"
51 #include "mono_picture_asset.h"
52 #include "openjpeg_image.h"
54 #include "picture_asset_writer.h"
55 #include "reel_mono_picture_asset.h"
56 #include "reel_asset.h"
59 #include <asdcp/KM_util.h>
60 #include <asdcp/KM_prng.h>
62 #include <libxml++/libxml++.h>
63 #include <boost/test/unit_test.hpp>
71 using std::shared_ptr;
72 using boost::optional;
75 boost::filesystem::path private_test;
76 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
84 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
85 private_test = boost::unit_test::framework::master_test_suite().argv[1];
88 using namespace boost::filesystem;
89 boost::system::error_code ec;
90 remove_all (xsd_test, ec);
91 boost::filesystem::create_directory (xsd_test);
92 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
93 copy_file (*i, xsd_test / i->path().filename());
99 check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore_tags, bool ignore_whitespace)
101 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
102 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
104 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
108 auto whitespace_content = [](xmlpp::Node* node) {
109 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
110 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
113 auto ref_children = ref->get_children ();
114 auto test_children = test->get_children ();
116 auto k = ref_children.begin ();
117 auto l = test_children.begin ();
118 while (k != ref_children.end() && l != test_children.end()) {
120 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
125 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
130 if (whitespace_content(*k) && ignore_whitespace) {
135 if (whitespace_content(*l) && ignore_whitespace) {
140 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
142 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
143 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
144 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
145 if (ref_el && test_el) {
146 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
149 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
150 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
151 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
152 if (ref_cn && test_cn) {
153 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
160 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
164 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
168 BOOST_REQUIRE (k == ref_children.end());
169 BOOST_REQUIRE (l == test_children.end());
171 auto ref_attributes = ref->get_attributes ();
172 auto test_attributes = test->get_attributes ();
173 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
175 auto m = ref_attributes.begin();
176 auto n = test_attributes.begin();
177 while (m != ref_attributes.end ()) {
178 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
179 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
187 check_xml (string ref, string test, list<string> ignore, bool ignore_whitespace)
189 xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
190 ref_parser->parse_memory (ref);
191 xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
192 xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
193 test_parser->parse_memory (test);
194 xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
196 check_xml (ref_root, test_root, ignore, ignore_whitespace);
200 check_file (boost::filesystem::path ref, boost::filesystem::path check)
202 uintmax_t N = boost::filesystem::file_size (ref);
203 BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
204 FILE* ref_file = dcp::fopen_boost (ref, "rb");
205 BOOST_REQUIRE (ref_file);
206 FILE* check_file = dcp::fopen_boost (check, "rb");
207 BOOST_REQUIRE (check_file);
209 int const buffer_size = 65536;
210 uint8_t* ref_buffer = new uint8_t[buffer_size];
211 uint8_t* check_buffer = new uint8_t[buffer_size];
214 error = "File " + check.string() + " differs from reference " + ref.string();
217 uintmax_t this_time = min (uintmax_t (buffer_size), N);
218 size_t r = fread (ref_buffer, 1, this_time, ref_file);
219 BOOST_CHECK_EQUAL (r, this_time);
220 r = fread (check_buffer, 1, this_time, check_file);
221 BOOST_CHECK_EQUAL (r, this_time);
223 BOOST_CHECK_MESSAGE (memcmp (ref_buffer, check_buffer, this_time) == 0, error);
224 if (memcmp (ref_buffer, check_buffer, this_time)) {
232 delete[] check_buffer;
239 RNGFixer::RNGFixer ()
241 Kumu::cth_test = true;
242 Kumu::FortunaRNG().Reset();
246 RNGFixer::~RNGFixer ()
248 Kumu::cth_test = false;
252 shared_ptr<dcp::MonoPictureAsset>
253 simple_picture (boost::filesystem::path path, string suffix)
255 dcp::MXFMetadata mxf_meta;
256 mxf_meta.company_name = "OpenDCP";
257 mxf_meta.product_name = "OpenDCP";
258 mxf_meta.product_version = "0.0.25";
260 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
261 mp->set_metadata (mxf_meta);
262 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
263 dcp::File j2c ("test/data/32x32_red_square.j2c");
264 for (int i = 0; i < 24; ++i) {
265 picture_writer->write (j2c.data (), j2c.size ());
267 picture_writer->finalize ();
273 shared_ptr<dcp::SoundAsset>
274 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language)
276 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
277 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-US"), dcp::SMPTE));
278 ms->_language = language;
279 ms->set_metadata (mxf_meta);
280 vector<dcp::Channel> active_channels;
281 active_channels.push_back (dcp::LEFT);
282 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
286 SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
287 BOOST_CHECK (sndfile);
288 float buffer[4096*6];
290 channels[0] = buffer;
292 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
293 sound_writer->write (channels, N);
299 sound_writer->finalize ();
306 make_simple (boost::filesystem::path path, int reels)
308 /* Some known metadata */
309 dcp::MXFMetadata mxf_meta;
310 mxf_meta.company_name = "OpenDCP";
311 mxf_meta.product_name = "OpenDCP";
312 mxf_meta.product_version = "0.0.25";
314 boost::filesystem::remove_all (path);
315 boost::filesystem::create_directories (path);
316 shared_ptr<dcp::DCP> d (new dcp::DCP (path));
317 shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
318 cpl->set_annotation_text ("A Test DCP");
319 cpl->set_issuer ("OpenDCP 0.0.25");
320 cpl->set_creator ("OpenDCP 0.0.25");
321 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
322 cpl->set_content_version (
323 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
326 for (int i = 0; i < reels; ++i) {
327 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
329 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix);
330 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US");
332 cpl->add (shared_ptr<dcp::Reel> (
334 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
335 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
345 shared_ptr<dcp::Subtitle>
348 return shared_ptr<dcp::Subtitle>(
349 new dcp::SubtitleString(
354 dcp::Colour(255, 255, 255),
357 dcp::Time(0, 0, 4, 0, 24),
358 dcp::Time(0, 0, 8, 0, 24),
366 dcp::Colour(255, 255, 255),
375 make_simple_with_interop_subs (boost::filesystem::path path)
377 shared_ptr<dcp::DCP> dcp = make_simple (path);
379 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
380 subs->add (simple_subtitle());
382 boost::filesystem::create_directory (path / "subs");
383 dcp::ArrayData data(4096);
384 subs->add_font ("afont", data);
385 subs->write (path / "subs" / "subs.xml");
387 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
388 dcp->cpls().front()->reels().front()->add (reel_subs);
395 make_simple_with_smpte_subs (boost::filesystem::path path)
397 shared_ptr<dcp::DCP> dcp = make_simple (path);
399 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
400 subs->add (simple_subtitle());
402 dcp::ArrayData data(4096);
403 subs->write (path / "subs.mxf");
405 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
406 dcp->cpls().front()->reels().front()->add (reel_subs);
413 make_simple_with_interop_ccaps (boost::filesystem::path path)
415 shared_ptr<dcp::DCP> dcp = make_simple (path);
417 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
418 subs->add (simple_subtitle());
419 subs->write (path / "ccap.xml");
421 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
422 dcp->cpls().front()->reels().front()->add (reel_caps);
429 make_simple_with_smpte_ccaps (boost::filesystem::path path)
431 shared_ptr<dcp::DCP> dcp = make_simple (path);
433 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
434 subs->add (simple_subtitle());
435 subs->write (path / "ccap.mxf");
437 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
438 dcp->cpls().front()->reels().front()->add (reel_caps);
444 shared_ptr<dcp::OpenJPEGImage>
447 shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
448 int const pixels = 1998 * 1080;
449 for (int i = 0; i < 3; ++i) {
450 memset (image->data(i), 0, pixels * sizeof(int));
456 shared_ptr<dcp::ReelAsset>
457 black_picture_asset (boost::filesystem::path dir, int frames)
459 shared_ptr<dcp::OpenJPEGImage> image = black_image ();
460 dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
461 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
463 shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
464 boost::filesystem::create_directories (dir);
465 shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
466 for (int i = 0; i < frames; ++i) {
467 writer->write (frame.data(), frame.size());
471 return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
475 BOOST_GLOBAL_FIXTURE (TestConfig);