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)
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 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
277 mp->set_metadata (mxf_meta);
278 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
280 dcp::Size const size (1998, 1080);
281 auto image = make_shared<dcp::OpenJPEGImage>(size);
282 for (int i = 0; i < 3; ++i) {
283 memset (image->data(i), 0, 2 * size.width * size.height);
285 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
287 for (int i = 0; i < frames; ++i) {
288 picture_writer->write (j2c.data(), j2c.size());
290 picture_writer->finalize ();
296 shared_ptr<dcp::SoundAsset>
297 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
299 int const channels = 6;
301 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
302 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
303 ms->_language = language;
304 ms->set_metadata (mxf_meta);
305 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
307 int const samples_per_frame = sample_rate / 24;
309 float* silence[channels];
310 for (auto i = 0; i < channels; ++i) {
311 silence[i] = new float[samples_per_frame];
312 memset (silence[i], 0, samples_per_frame * sizeof(float));
315 for (auto i = 0; i < frames; ++i) {
316 sound_writer->write (silence, samples_per_frame);
319 sound_writer->finalize ();
321 for (auto i = 0; i < channels; ++i) {
330 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard)
332 /* Some known metadata */
333 dcp::MXFMetadata mxf_meta;
334 mxf_meta.company_name = "OpenDCP";
335 mxf_meta.product_name = "OpenDCP";
336 mxf_meta.product_version = "0.0.25";
338 boost::filesystem::remove_all (path);
339 boost::filesystem::create_directories (path);
340 auto d = make_shared<dcp::DCP>(path);
341 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
342 cpl->set_annotation_text ("A Test DCP");
343 cpl->set_issuer ("OpenDCP 0.0.25");
344 cpl->set_creator ("OpenDCP 0.0.25");
345 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
346 cpl->set_content_version (
347 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
349 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
350 cpl->set_main_sound_sample_rate(48000);
351 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
352 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
353 cpl->set_version_number(1);
355 for (int i = 0; i < reels; ++i) {
356 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
358 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
359 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
361 auto reel = make_shared<dcp::Reel>(
362 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
363 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
366 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
368 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
370 if (i == reels - 1) {
371 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
383 shared_ptr<dcp::Subtitle>
386 return make_shared<dcp::SubtitleString>(
391 dcp::Colour(255, 255, 255),
394 dcp::Time(0, 0, 4, 0, 24),
395 dcp::Time(0, 0, 8, 0, 24),
403 dcp::Colour(255, 255, 255),
410 shared_ptr<dcp::ReelMarkersAsset>
411 simple_markers (int frames)
413 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
414 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
415 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
421 make_simple_with_interop_subs (boost::filesystem::path path)
423 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
425 auto subs = make_shared<dcp::InteropSubtitleAsset>();
426 subs->add (simple_subtitle());
428 boost::filesystem::create_directory (path / "subs");
429 dcp::ArrayData data(4096);
430 subs->add_font ("afont", data);
431 subs->write (path / "subs" / "subs.xml");
433 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
434 dcp->cpls().front()->reels().front()->add (reel_subs);
441 make_simple_with_smpte_subs (boost::filesystem::path path)
443 auto dcp = make_simple (path, 1, 192);
445 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
446 subs->set_language (dcp::LanguageTag("de-DE"));
447 subs->set_start_time (dcp::Time());
448 subs->add (simple_subtitle());
450 subs->write (path / "subs.mxf");
452 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
453 dcp->cpls().front()->reels().front()->add (reel_subs);
460 make_simple_with_interop_ccaps (boost::filesystem::path path)
462 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
464 auto subs = make_shared<dcp::InteropSubtitleAsset>();
465 subs->add (simple_subtitle());
466 subs->write (path / "ccap.xml");
468 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
469 dcp->cpls()[0]->reels()[0]->add (reel_caps);
476 make_simple_with_smpte_ccaps (boost::filesystem::path path)
478 auto dcp = make_simple (path, 1, 192);
480 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
481 subs->set_language (dcp::LanguageTag("de-DE"));
482 subs->set_start_time (dcp::Time());
483 subs->add (simple_subtitle());
484 subs->write (path / "ccap.mxf");
486 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
487 dcp->cpls()[0]->reels()[0]->add(reel_caps);
493 shared_ptr<dcp::OpenJPEGImage>
494 black_image (dcp::Size size)
496 auto image = make_shared<dcp::OpenJPEGImage>(size);
497 int const pixels = size.width * size.height;
498 for (int i = 0; i < 3; ++i) {
499 memset (image->data(i), 0, pixels * sizeof(int));
505 shared_ptr<dcp::ReelAsset>
506 black_picture_asset (boost::filesystem::path dir, int frames)
508 auto image = black_image ();
509 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
510 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
512 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
513 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
514 boost::filesystem::create_directories (dir);
515 auto writer = asset->start_write (dir / "pic.mxf", true);
516 for (int i = 0; i < frames; ++i) {
517 writer->write (frame.data(), frame.size());
521 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
525 boost::filesystem::path
526 find_file (boost::filesystem::path dir, string filename_part)
528 boost::optional<boost::filesystem::path> found;
529 for (auto i: boost::filesystem::directory_iterator(dir)) {
530 if (i.path().filename().string().find(filename_part) != string::npos) {
531 BOOST_REQUIRE (!found);
535 BOOST_REQUIRE (found);
540 BOOST_GLOBAL_FIXTURE (TestConfig);