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"
52 #include "j2k_transcode.h"
53 #include "picture_asset_writer.h"
54 #include "reel_mono_picture_asset.h"
55 #include "reel_asset.h"
58 #include "reel_markers_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>
70 using std::shared_ptr;
71 using std::make_shared;
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, vector<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, vector<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 size = boost::filesystem::file_size (ref);
203 BOOST_CHECK_EQUAL (size, 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];
216 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
217 size_t r = fread (ref_buffer, 1, this_time, ref_file);
218 BOOST_CHECK_EQUAL (r, this_time);
219 r = fread (check_buffer, 1, this_time, check_file);
220 BOOST_CHECK_EQUAL (r, this_time);
222 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
223 for (int i = 0; i < buffer_size; ++i) {
224 if (ref_buffer[i] != check_buffer[i]) {
225 BOOST_CHECK_MESSAGE (
227 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
239 delete[] check_buffer;
246 RNGFixer::RNGFixer ()
248 Kumu::cth_test = true;
249 Kumu::FortunaRNG().Reset();
253 RNGFixer::~RNGFixer ()
255 Kumu::cth_test = false;
259 shared_ptr<dcp::MonoPictureAsset>
260 simple_picture (boost::filesystem::path path, string suffix, int frames)
262 dcp::MXFMetadata mxf_meta;
263 mxf_meta.company_name = "OpenDCP";
264 mxf_meta.product_name = "OpenDCP";
265 mxf_meta.product_version = "0.0.25";
267 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
268 mp->set_metadata (mxf_meta);
269 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
271 dcp::Size const size (1998, 1080);
272 auto image = make_shared<dcp::OpenJPEGImage>(size);
273 for (int i = 0; i < 3; ++i) {
274 memset (image->data(i), 0, 2 * size.width * size.height);
276 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
278 for (int i = 0; i < frames; ++i) {
279 picture_writer->write (j2c.data(), j2c.size());
281 picture_writer->finalize ();
287 shared_ptr<dcp::SoundAsset>
288 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
290 int const channels = 6;
292 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
293 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
294 ms->_language = language;
295 ms->set_metadata (mxf_meta);
296 vector<dcp::Channel> active_channels;
297 active_channels.push_back (dcp::Channel::LEFT);
298 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
300 int const samples_per_frame = sample_rate / 24;
302 float* silence[channels];
303 for (auto i = 0; i < channels; ++i) {
304 silence[i] = new float[samples_per_frame];
305 memset (silence[i], 0, samples_per_frame * sizeof(float));
308 for (auto i = 0; i < frames; ++i) {
309 sound_writer->write (silence, samples_per_frame);
312 sound_writer->finalize ();
314 for (auto i = 0; i < channels; ++i) {
323 make_simple (boost::filesystem::path path, int reels, int frames)
325 /* Some known metadata */
326 dcp::MXFMetadata mxf_meta;
327 mxf_meta.company_name = "OpenDCP";
328 mxf_meta.product_name = "OpenDCP";
329 mxf_meta.product_version = "0.0.25";
331 boost::filesystem::remove_all (path);
332 boost::filesystem::create_directories (path);
333 auto d = make_shared<dcp::DCP>(path);
334 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
335 cpl->set_annotation_text ("A Test DCP");
336 cpl->set_issuer ("OpenDCP 0.0.25");
337 cpl->set_creator ("OpenDCP 0.0.25");
338 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
339 cpl->set_content_version (
340 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
342 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
343 cpl->set_main_sound_sample_rate(48000);
344 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
345 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
346 cpl->set_version_number(1);
348 for (int i = 0; i < reels; ++i) {
349 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
351 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
352 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
354 auto reel = make_shared<dcp::Reel>(
355 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
356 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
359 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
361 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
363 if (i == reels - 1) {
364 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
376 shared_ptr<dcp::Subtitle>
379 return make_shared<dcp::SubtitleString>(
384 dcp::Colour(255, 255, 255),
387 dcp::Time(0, 0, 4, 0, 24),
388 dcp::Time(0, 0, 8, 0, 24),
396 dcp::Colour(255, 255, 255),
403 shared_ptr<dcp::ReelMarkersAsset>
404 simple_markers (int frames)
406 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
407 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
408 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
414 make_simple_with_interop_subs (boost::filesystem::path path)
416 shared_ptr<dcp::DCP> dcp = make_simple (path);
418 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
419 subs->add (simple_subtitle());
421 boost::filesystem::create_directory (path / "subs");
422 dcp::ArrayData data(4096);
423 subs->add_font ("afont", data);
424 subs->write (path / "subs" / "subs.xml");
426 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
427 dcp->cpls().front()->reels().front()->add (reel_subs);
434 make_simple_with_smpte_subs (boost::filesystem::path path)
436 shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
438 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
439 subs->set_language (dcp::LanguageTag("de-DE"));
440 subs->set_start_time (dcp::Time());
441 subs->add (simple_subtitle());
443 subs->write (path / "subs.mxf");
445 shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
446 dcp->cpls().front()->reels().front()->add (reel_subs);
453 make_simple_with_interop_ccaps (boost::filesystem::path path)
455 shared_ptr<dcp::DCP> dcp = make_simple (path);
457 shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
458 subs->add (simple_subtitle());
459 subs->write (path / "ccap.xml");
461 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
462 dcp->cpls().front()->reels().front()->add (reel_caps);
469 make_simple_with_smpte_ccaps (boost::filesystem::path path)
471 shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
473 shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
474 subs->set_language (dcp::LanguageTag("de-DE"));
475 subs->set_start_time (dcp::Time());
476 subs->add (simple_subtitle());
477 subs->write (path / "ccap.mxf");
479 shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
480 dcp->cpls().front()->reels().front()->add (reel_caps);
486 shared_ptr<dcp::OpenJPEGImage>
487 black_image (dcp::Size size)
489 shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
490 int const pixels = size.width * size.height;
491 for (int i = 0; i < 3; ++i) {
492 memset (image->data(i), 0, pixels * sizeof(int));
498 shared_ptr<dcp::ReelAsset>
499 black_picture_asset (boost::filesystem::path dir, int frames)
501 shared_ptr<dcp::OpenJPEGImage> image = black_image ();
502 dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
503 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
505 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
506 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
507 boost::filesystem::create_directories (dir);
508 shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
509 for (int i = 0; i < frames; ++i) {
510 writer->write (frame.data(), frame.size());
514 return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
518 BOOST_GLOBAL_FIXTURE (TestConfig);