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 "mono_picture_asset.h"
41 #include "picture_asset_writer.h"
43 #include "reel_mono_picture_asset.h"
44 #include "reel_sound_asset.h"
45 #include "reel_closed_caption_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "sound_asset.h"
48 #include "sound_asset_writer.h"
49 #include "smpte_subtitle_asset.h"
50 #include "mono_picture_asset.h"
51 #include "openjpeg_image.h"
53 #include "picture_asset_writer.h"
54 #include "reel_mono_picture_asset.h"
55 #include "reel_asset.h"
58 #include <asdcp/KM_util.h>
59 #include <asdcp/KM_prng.h>
61 #include <libxml++/libxml++.h>
62 #include <boost/test/unit_test.hpp>
70 using std::shared_ptr;
71 using boost::optional;
74 boost::filesystem::path private_test;
75 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
83 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
84 private_test = boost::unit_test::framework::master_test_suite().argv[1];
87 using namespace boost::filesystem;
88 boost::system::error_code ec;
89 remove_all (xsd_test, ec);
90 boost::filesystem::create_directory (xsd_test);
91 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
92 copy_file (*i, xsd_test / i->path().filename());
98 check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore_tags, bool ignore_whitespace)
100 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
101 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
103 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
107 auto whitespace_content = [](xmlpp::Node* node) {
108 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
109 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
112 auto ref_children = ref->get_children ();
113 auto test_children = test->get_children ();
115 auto k = ref_children.begin ();
116 auto l = test_children.begin ();
117 while (k != ref_children.end() && l != test_children.end()) {
119 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
124 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
129 if (whitespace_content(*k) && ignore_whitespace) {
134 if (whitespace_content(*l) && ignore_whitespace) {
139 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
141 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
142 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
143 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
144 if (ref_el && test_el) {
145 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
148 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
149 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
150 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
151 if (ref_cn && test_cn) {
152 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
159 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
163 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
167 BOOST_REQUIRE (k == ref_children.end());
168 BOOST_REQUIRE (l == test_children.end());
170 auto ref_attributes = ref->get_attributes ();
171 auto test_attributes = test->get_attributes ();
172 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
174 auto m = ref_attributes.begin();
175 auto n = test_attributes.begin();
176 while (m != ref_attributes.end ()) {
177 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
178 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
186 check_xml (string ref, string test, list<string> ignore, bool ignore_whitespace)
188 xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
189 ref_parser->parse_memory (ref);
190 xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
191 xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
192 test_parser->parse_memory (test);
193 xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
195 check_xml (ref_root, test_root, ignore, ignore_whitespace);
199 check_file (boost::filesystem::path ref, boost::filesystem::path check)
201 uintmax_t size = boost::filesystem::file_size (ref);
202 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
203 FILE* ref_file = dcp::fopen_boost (ref, "rb");
204 BOOST_REQUIRE (ref_file);
205 FILE* check_file = dcp::fopen_boost (check, "rb");
206 BOOST_REQUIRE (check_file);
208 int const buffer_size = 65536;
209 uint8_t* ref_buffer = new uint8_t[buffer_size];
210 uint8_t* check_buffer = new uint8_t[buffer_size];
215 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
216 size_t r = fread (ref_buffer, 1, this_time, ref_file);
217 BOOST_CHECK_EQUAL (r, this_time);
218 r = fread (check_buffer, 1, this_time, check_file);
219 BOOST_CHECK_EQUAL (r, this_time);
221 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
222 for (int i = 0; i < buffer_size; ++i) {
223 if (ref_buffer[i] != check_buffer[i]) {
224 BOOST_CHECK_MESSAGE (
226 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
238 delete[] check_buffer;
245 RNGFixer::RNGFixer ()
247 Kumu::cth_test = true;
248 Kumu::FortunaRNG().Reset();
252 RNGFixer::~RNGFixer ()
254 Kumu::cth_test = false;
258 shared_ptr<dcp::MonoPictureAsset>
259 simple_picture (boost::filesystem::path path, string suffix)
261 dcp::MXFMetadata mxf_meta;
262 mxf_meta.company_name = "OpenDCP";
263 mxf_meta.product_name = "OpenDCP";
264 mxf_meta.product_version = "0.0.25";
266 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
267 mp->set_metadata (mxf_meta);
268 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
269 dcp::ArrayData j2c ("test/data/flat_red.j2c");
270 for (int i = 0; i < 24; ++i) {
271 picture_writer->write (j2c.data (), j2c.size ());
273 picture_writer->finalize ();
279 shared_ptr<dcp::SoundAsset>
280 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language)
282 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
283 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-US"), dcp::SMPTE));
284 ms->_language = language;
285 ms->set_metadata (mxf_meta);
286 vector<dcp::Channel> active_channels;
287 active_channels.push_back (dcp::LEFT);
288 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
292 SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
293 BOOST_CHECK (sndfile);
294 float buffer[4096*6];
296 channels[0] = buffer;
298 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
299 sound_writer->write (channels, N);
305 sound_writer->finalize ();
312 make_simple (boost::filesystem::path path, int reels)
314 /* Some known metadata */
315 dcp::MXFMetadata mxf_meta;
316 mxf_meta.company_name = "OpenDCP";
317 mxf_meta.product_name = "OpenDCP";
318 mxf_meta.product_version = "0.0.25";
320 boost::filesystem::remove_all (path);
321 boost::filesystem::create_directories (path);
322 shared_ptr<dcp::DCP> d (new dcp::DCP (path));
323 shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
324 cpl->set_annotation_text ("A Test DCP");
325 cpl->set_issuer ("OpenDCP 0.0.25");
326 cpl->set_creator ("OpenDCP 0.0.25");
327 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
328 cpl->set_content_version (
329 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
332 for (int i = 0; i < reels; ++i) {
333 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
335 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix);
336 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US");
338 cpl->add (shared_ptr<dcp::Reel> (
340 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
341 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
351 shared_ptr<dcp::Subtitle>
354 return shared_ptr<dcp::Subtitle>(
355 new dcp::SubtitleString(
360 dcp::Colour(255, 255, 255),
363 dcp::Time(0, 0, 4, 0, 24),
364 dcp::Time(0, 0, 8, 0, 24),
372 dcp::Colour(255, 255, 255),
381 make_simple_with_interop_subs (boost::filesystem::path path)
383 shared_ptr<dcp::DCP> dcp = make_simple (path);
385 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
386 subs->add (simple_subtitle());
388 boost::filesystem::create_directory (path / "subs");
389 dcp::ArrayData data(4096);
390 subs->add_font ("afont", data);
391 subs->write (path / "subs" / "subs.xml");
393 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
394 dcp->cpls().front()->reels().front()->add (reel_subs);
401 make_simple_with_smpte_subs (boost::filesystem::path path)
403 shared_ptr<dcp::DCP> dcp = make_simple (path);
405 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
406 subs->set_language (dcp::LanguageTag("de-DE"));
407 subs->set_start_time (dcp::Time());
408 subs->add (simple_subtitle());
410 subs->write (path / "subs.mxf");
412 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
413 dcp->cpls().front()->reels().front()->add (reel_subs);
420 make_simple_with_interop_ccaps (boost::filesystem::path path)
422 shared_ptr<dcp::DCP> dcp = make_simple (path);
424 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
425 subs->add (simple_subtitle());
426 subs->write (path / "ccap.xml");
428 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
429 dcp->cpls().front()->reels().front()->add (reel_caps);
436 make_simple_with_smpte_ccaps (boost::filesystem::path path)
438 shared_ptr<dcp::DCP> dcp = make_simple (path);
440 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
441 subs->set_language (dcp::LanguageTag("de-DE"));
442 subs->set_start_time (dcp::Time());
443 subs->add (simple_subtitle());
444 subs->write (path / "ccap.mxf");
446 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
447 dcp->cpls().front()->reels().front()->add (reel_caps);
453 shared_ptr<dcp::OpenJPEGImage>
454 black_image (dcp::Size size)
456 shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
457 int const pixels = size.width * size.height;
458 for (int i = 0; i < 3; ++i) {
459 memset (image->data(i), 0, pixels * sizeof(int));
465 shared_ptr<dcp::ReelAsset>
466 black_picture_asset (boost::filesystem::path dir, int frames)
468 shared_ptr<dcp::OpenJPEGImage> image = black_image ();
469 dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
470 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
472 shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
473 boost::filesystem::create_directories (dir);
474 shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
475 for (int i = 0; i < frames; ++i) {
476 writer->write (frame.data(), frame.size());
480 return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
484 BOOST_GLOBAL_FIXTURE (TestConfig);