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>
69 using std::shared_ptr;
70 using boost::optional;
73 boost::filesystem::path private_test;
74 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
82 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
83 private_test = boost::unit_test::framework::master_test_suite().argv[1];
86 using namespace boost::filesystem;
87 boost::system::error_code ec;
88 remove_all (xsd_test, ec);
89 boost::filesystem::create_directory (xsd_test);
90 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
91 copy_file (*i, xsd_test / i->path().filename());
97 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
99 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
100 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
102 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
106 auto whitespace_content = [](xmlpp::Node* node) {
107 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
108 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
111 auto ref_children = ref->get_children ();
112 auto test_children = test->get_children ();
114 auto k = ref_children.begin ();
115 auto l = test_children.begin ();
116 while (k != ref_children.end() && l != test_children.end()) {
118 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
123 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
128 if (whitespace_content(*k) && ignore_whitespace) {
133 if (whitespace_content(*l) && ignore_whitespace) {
138 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
140 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
141 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
142 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
143 if (ref_el && test_el) {
144 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
147 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
148 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
149 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
150 if (ref_cn && test_cn) {
151 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
158 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
162 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
166 BOOST_REQUIRE (k == ref_children.end());
167 BOOST_REQUIRE (l == test_children.end());
169 auto ref_attributes = ref->get_attributes ();
170 auto test_attributes = test->get_attributes ();
171 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
173 auto m = ref_attributes.begin();
174 auto n = test_attributes.begin();
175 while (m != ref_attributes.end ()) {
176 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
177 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
185 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
187 xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
188 ref_parser->parse_memory (ref);
189 xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
190 xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
191 test_parser->parse_memory (test);
192 xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
194 check_xml (ref_root, test_root, ignore, ignore_whitespace);
198 check_file (boost::filesystem::path ref, boost::filesystem::path check)
200 uintmax_t size = boost::filesystem::file_size (ref);
201 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
202 FILE* ref_file = dcp::fopen_boost (ref, "rb");
203 BOOST_REQUIRE (ref_file);
204 FILE* check_file = dcp::fopen_boost (check, "rb");
205 BOOST_REQUIRE (check_file);
207 int const buffer_size = 65536;
208 uint8_t* ref_buffer = new uint8_t[buffer_size];
209 uint8_t* check_buffer = new uint8_t[buffer_size];
214 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
215 size_t r = fread (ref_buffer, 1, this_time, ref_file);
216 BOOST_CHECK_EQUAL (r, this_time);
217 r = fread (check_buffer, 1, this_time, check_file);
218 BOOST_CHECK_EQUAL (r, this_time);
220 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
221 for (int i = 0; i < buffer_size; ++i) {
222 if (ref_buffer[i] != check_buffer[i]) {
223 BOOST_CHECK_MESSAGE (
225 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
237 delete[] check_buffer;
244 RNGFixer::RNGFixer ()
246 Kumu::cth_test = true;
247 Kumu::FortunaRNG().Reset();
251 RNGFixer::~RNGFixer ()
253 Kumu::cth_test = false;
257 shared_ptr<dcp::MonoPictureAsset>
258 simple_picture (boost::filesystem::path path, string suffix, int frames)
260 dcp::MXFMetadata mxf_meta;
261 mxf_meta.company_name = "OpenDCP";
262 mxf_meta.product_name = "OpenDCP";
263 mxf_meta.product_version = "0.0.25";
265 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
266 mp->set_metadata (mxf_meta);
267 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
268 dcp::ArrayData j2c ("test/data/flat_red.j2c");
269 for (int i = 0; i < frames; ++i) {
270 picture_writer->write (j2c.data (), j2c.size ());
272 picture_writer->finalize ();
278 shared_ptr<dcp::SoundAsset>
279 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
281 int const channels = 1;
283 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
284 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::SMPTE));
285 ms->_language = language;
286 ms->set_metadata (mxf_meta);
287 vector<dcp::Channel> active_channels;
288 active_channels.push_back (dcp::LEFT);
289 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
291 int const samples_per_frame = sample_rate / 24;
293 float* silence[channels];
294 for (auto i = 0; i < channels; ++i) {
295 silence[i] = new float[samples_per_frame];
296 memset (silence[i], 0, samples_per_frame * sizeof(float));
299 for (auto i = 0; i < frames; ++i) {
300 sound_writer->write (silence, samples_per_frame);
303 sound_writer->finalize ();
305 for (auto i = 0; i < channels; ++i) {
314 make_simple (boost::filesystem::path path, int reels, int frames)
316 /* Some known metadata */
317 dcp::MXFMetadata mxf_meta;
318 mxf_meta.company_name = "OpenDCP";
319 mxf_meta.product_name = "OpenDCP";
320 mxf_meta.product_version = "0.0.25";
322 boost::filesystem::remove_all (path);
323 boost::filesystem::create_directories (path);
324 shared_ptr<dcp::DCP> d (new dcp::DCP (path));
325 shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
326 cpl->set_annotation_text ("A Test DCP");
327 cpl->set_issuer ("OpenDCP 0.0.25");
328 cpl->set_creator ("OpenDCP 0.0.25");
329 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
330 cpl->set_content_version (
331 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
334 for (int i = 0; i < reels; ++i) {
335 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
337 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
338 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
340 cpl->add (shared_ptr<dcp::Reel> (
342 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
343 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
353 shared_ptr<dcp::Subtitle>
356 return shared_ptr<dcp::Subtitle>(
357 new dcp::SubtitleString(
362 dcp::Colour(255, 255, 255),
365 dcp::Time(0, 0, 4, 0, 24),
366 dcp::Time(0, 0, 8, 0, 24),
374 dcp::Colour(255, 255, 255),
383 make_simple_with_interop_subs (boost::filesystem::path path)
385 shared_ptr<dcp::DCP> dcp = make_simple (path);
387 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
388 subs->add (simple_subtitle());
390 boost::filesystem::create_directory (path / "subs");
391 dcp::ArrayData data(4096);
392 subs->add_font ("afont", data);
393 subs->write (path / "subs" / "subs.xml");
395 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
396 dcp->cpls().front()->reels().front()->add (reel_subs);
403 make_simple_with_smpte_subs (boost::filesystem::path path)
405 shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
407 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
408 subs->set_language (dcp::LanguageTag("de-DE"));
409 subs->set_start_time (dcp::Time());
410 subs->add (simple_subtitle());
412 subs->write (path / "subs.mxf");
414 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
415 dcp->cpls().front()->reels().front()->add (reel_subs);
422 make_simple_with_interop_ccaps (boost::filesystem::path path)
424 shared_ptr<dcp::DCP> dcp = make_simple (path);
426 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
427 subs->add (simple_subtitle());
428 subs->write (path / "ccap.xml");
430 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
431 dcp->cpls().front()->reels().front()->add (reel_caps);
438 make_simple_with_smpte_ccaps (boost::filesystem::path path)
440 shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
442 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
443 subs->set_language (dcp::LanguageTag("de-DE"));
444 subs->set_start_time (dcp::Time());
445 subs->add (simple_subtitle());
446 subs->write (path / "ccap.mxf");
448 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
449 dcp->cpls().front()->reels().front()->add (reel_caps);
455 shared_ptr<dcp::OpenJPEGImage>
456 black_image (dcp::Size size)
458 shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
459 int const pixels = size.width * size.height;
460 for (int i = 0; i < 3; ++i) {
461 memset (image->data(i), 0, pixels * sizeof(int));
467 shared_ptr<dcp::ReelAsset>
468 black_picture_asset (boost::filesystem::path dir, int frames)
470 shared_ptr<dcp::OpenJPEGImage> image = black_image ();
471 dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
472 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
474 shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
475 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
476 boost::filesystem::create_directories (dir);
477 shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
478 for (int i = 0; i < frames; ++i) {
479 writer->write (frame.data(), frame.size());
483 return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
487 BOOST_GLOBAL_FIXTURE (TestConfig);