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"
61 #include <asdcp/KM_util.h>
62 #include <asdcp/KM_prng.h>
64 #include <libxml++/libxml++.h>
65 #include <boost/test/unit_test.hpp>
73 using std::shared_ptr;
74 using std::make_shared;
75 using boost::optional;
78 boost::filesystem::path private_test;
79 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
87 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
88 private_test = boost::unit_test::framework::master_test_suite().argv[1];
91 using namespace boost::filesystem;
92 boost::system::error_code ec;
93 remove_all (xsd_test, ec);
94 boost::filesystem::create_directory (xsd_test);
95 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
96 copy_file (*i, xsd_test / i->path().filename());
103 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
105 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
106 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
108 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
112 auto whitespace_content = [](xmlpp::Node* node) {
113 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
114 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
117 auto ref_children = ref->get_children ();
118 auto test_children = test->get_children ();
120 auto k = ref_children.begin ();
121 auto l = test_children.begin ();
122 while (k != ref_children.end() && l != test_children.end()) {
124 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
129 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
134 if (whitespace_content(*k) && ignore_whitespace) {
139 if (whitespace_content(*l) && ignore_whitespace) {
144 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
146 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
147 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
148 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
149 if (ref_el && test_el) {
150 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
153 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
154 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
155 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
156 if (ref_cn && test_cn) {
157 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
164 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
168 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
172 BOOST_REQUIRE (k == ref_children.end());
173 BOOST_REQUIRE (l == test_children.end());
175 auto ref_attributes = ref->get_attributes ();
176 auto test_attributes = test->get_attributes ();
177 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
179 auto m = ref_attributes.begin();
180 auto n = test_attributes.begin();
181 while (m != ref_attributes.end ()) {
182 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
183 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
191 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
193 auto ref_parser = new xmlpp::DomParser ();
194 ref_parser->parse_memory (ref);
195 auto ref_root = ref_parser->get_document()->get_root_node ();
196 auto test_parser = new xmlpp::DomParser ();
197 test_parser->parse_memory (test);
198 auto test_root = test_parser->get_document()->get_root_node ();
200 check_xml (ref_root, test_root, ignore, ignore_whitespace);
204 check_file (boost::filesystem::path ref, boost::filesystem::path check)
206 uintmax_t size = boost::filesystem::file_size (ref);
207 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
208 auto ref_file = dcp::fopen_boost (ref, "rb");
209 BOOST_REQUIRE (ref_file);
210 auto check_file = dcp::fopen_boost (check, "rb");
211 BOOST_REQUIRE (check_file);
213 int const buffer_size = 65536;
214 auto ref_buffer = new uint8_t[buffer_size];
215 auto check_buffer = new uint8_t[buffer_size];
220 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
221 size_t r = fread (ref_buffer, 1, this_time, ref_file);
222 BOOST_CHECK_EQUAL (r, this_time);
223 r = fread (check_buffer, 1, this_time, check_file);
224 BOOST_CHECK_EQUAL (r, this_time);
226 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
227 for (int i = 0; i < buffer_size; ++i) {
228 if (ref_buffer[i] != check_buffer[i]) {
229 BOOST_CHECK_MESSAGE (
231 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
243 delete[] check_buffer;
250 RNGFixer::RNGFixer ()
252 Kumu::cth_test = true;
253 Kumu::FortunaRNG().Reset();
257 RNGFixer::~RNGFixer ()
259 Kumu::cth_test = false;
263 shared_ptr<dcp::MonoPictureAsset>
264 simple_picture (boost::filesystem::path path, string suffix, int frames)
266 dcp::MXFMetadata mxf_meta;
267 mxf_meta.company_name = "OpenDCP";
268 mxf_meta.product_name = "OpenDCP";
269 mxf_meta.product_version = "0.0.25";
271 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
272 mp->set_metadata (mxf_meta);
273 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
275 dcp::Size const size (1998, 1080);
276 auto image = make_shared<dcp::OpenJPEGImage>(size);
277 for (int i = 0; i < 3; ++i) {
278 memset (image->data(i), 0, 2 * size.width * size.height);
280 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
282 for (int i = 0; i < frames; ++i) {
283 picture_writer->write (j2c.data(), j2c.size());
285 picture_writer->finalize ();
291 shared_ptr<dcp::SoundAsset>
292 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
294 int const channels = 6;
296 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
297 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
298 ms->_language = language;
299 ms->set_metadata (mxf_meta);
300 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
302 int const samples_per_frame = sample_rate / 24;
304 float* silence[channels];
305 for (auto i = 0; i < channels; ++i) {
306 silence[i] = new float[samples_per_frame];
307 memset (silence[i], 0, samples_per_frame * sizeof(float));
310 for (auto i = 0; i < frames; ++i) {
311 sound_writer->write (silence, samples_per_frame);
314 sound_writer->finalize ();
316 for (auto i = 0; i < channels; ++i) {
325 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard)
327 /* Some known metadata */
328 dcp::MXFMetadata mxf_meta;
329 mxf_meta.company_name = "OpenDCP";
330 mxf_meta.product_name = "OpenDCP";
331 mxf_meta.product_version = "0.0.25";
333 boost::filesystem::remove_all (path);
334 boost::filesystem::create_directories (path);
335 auto d = make_shared<dcp::DCP>(path);
336 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
337 cpl->set_annotation_text ("A Test DCP");
338 cpl->set_issuer ("OpenDCP 0.0.25");
339 cpl->set_creator ("OpenDCP 0.0.25");
340 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
341 cpl->set_content_version (
342 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
344 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
345 cpl->set_main_sound_sample_rate(48000);
346 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
347 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
348 cpl->set_version_number(1);
350 for (int i = 0; i < reels; ++i) {
351 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
353 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
354 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
356 auto reel = make_shared<dcp::Reel>(
357 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
358 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
361 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
363 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
365 if (i == reels - 1) {
366 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
378 shared_ptr<dcp::Subtitle>
381 return make_shared<dcp::SubtitleString>(
386 dcp::Colour(255, 255, 255),
389 dcp::Time(0, 0, 4, 0, 24),
390 dcp::Time(0, 0, 8, 0, 24),
398 dcp::Colour(255, 255, 255),
405 shared_ptr<dcp::ReelMarkersAsset>
406 simple_markers (int frames)
408 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
409 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
410 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
416 make_simple_with_interop_subs (boost::filesystem::path path)
418 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
420 auto subs = make_shared<dcp::InteropSubtitleAsset>();
421 subs->add (simple_subtitle());
423 boost::filesystem::create_directory (path / "subs");
424 dcp::ArrayData data(4096);
425 subs->add_font ("afont", data);
426 subs->write (path / "subs" / "subs.xml");
428 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
429 dcp->cpls().front()->reels().front()->add (reel_subs);
436 make_simple_with_smpte_subs (boost::filesystem::path path)
438 auto dcp = make_simple (path, 1, 192);
440 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
441 subs->set_language (dcp::LanguageTag("de-DE"));
442 subs->set_start_time (dcp::Time());
443 subs->add (simple_subtitle());
445 subs->write (path / "subs.mxf");
447 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
448 dcp->cpls().front()->reels().front()->add (reel_subs);
455 make_simple_with_interop_ccaps (boost::filesystem::path path)
457 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
459 auto subs = make_shared<dcp::InteropSubtitleAsset>();
460 subs->add (simple_subtitle());
461 subs->write (path / "ccap.xml");
463 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
464 dcp->cpls()[0]->reels()[0]->add (reel_caps);
471 make_simple_with_smpte_ccaps (boost::filesystem::path path)
473 auto dcp = make_simple (path, 1, 192);
475 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
476 subs->set_language (dcp::LanguageTag("de-DE"));
477 subs->set_start_time (dcp::Time());
478 subs->add (simple_subtitle());
479 subs->write (path / "ccap.mxf");
481 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
482 dcp->cpls()[0]->reels()[0]->add(reel_caps);
488 shared_ptr<dcp::OpenJPEGImage>
489 black_image (dcp::Size size)
491 auto image = make_shared<dcp::OpenJPEGImage>(size);
492 int const pixels = size.width * size.height;
493 for (int i = 0; i < 3; ++i) {
494 memset (image->data(i), 0, pixels * sizeof(int));
500 shared_ptr<dcp::ReelAsset>
501 black_picture_asset (boost::filesystem::path dir, int frames)
503 auto image = black_image ();
504 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
505 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
507 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
508 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
509 boost::filesystem::create_directories (dir);
510 auto writer = asset->start_write (dir / "pic.mxf", true);
511 for (int i = 0; i < frames; ++i) {
512 writer->write (frame.data(), frame.size());
516 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
520 boost::filesystem::path
521 find_file (boost::filesystem::path dir, string filename_part)
523 boost::optional<boost::filesystem::path> found;
524 for (auto i: boost::filesystem::directory_iterator(dir)) {
525 if (i.path().filename().string().find(filename_part) != string::npos) {
526 BOOST_REQUIRE (!found);
530 BOOST_REQUIRE (found);
535 BOOST_GLOBAL_FIXTURE (TestConfig);