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_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_subtitle_asset.h"
54 #include "reel_sound_asset.h"
55 #include "smpte_subtitle_asset.h"
56 #include "sound_asset.h"
57 #include "sound_asset_writer.h"
60 #include <asdcp/KM_util.h>
61 #include <asdcp/KM_prng.h>
63 #include <libxml++/libxml++.h>
64 #include <boost/test/unit_test.hpp>
72 using std::shared_ptr;
73 using std::make_shared;
74 using boost::optional;
77 boost::filesystem::path private_test;
78 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
86 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
87 private_test = boost::unit_test::framework::master_test_suite().argv[1];
90 using namespace boost::filesystem;
91 boost::system::error_code ec;
92 remove_all (xsd_test, ec);
93 boost::filesystem::create_directory (xsd_test);
94 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
95 copy_file (*i, xsd_test / i->path().filename());
102 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
104 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
105 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
107 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
111 auto whitespace_content = [](xmlpp::Node* node) {
112 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
113 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
116 auto ref_children = ref->get_children ();
117 auto test_children = test->get_children ();
119 auto k = ref_children.begin ();
120 auto l = test_children.begin ();
121 while (k != ref_children.end() && l != test_children.end()) {
123 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
128 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
133 if (whitespace_content(*k) && ignore_whitespace) {
138 if (whitespace_content(*l) && ignore_whitespace) {
143 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
145 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
146 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
147 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
148 if (ref_el && test_el) {
149 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
152 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
153 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
154 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
155 if (ref_cn && test_cn) {
156 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
163 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
167 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
171 BOOST_REQUIRE (k == ref_children.end());
172 BOOST_REQUIRE (l == test_children.end());
174 auto ref_attributes = ref->get_attributes ();
175 auto test_attributes = test->get_attributes ();
176 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
178 auto m = ref_attributes.begin();
179 auto n = test_attributes.begin();
180 while (m != ref_attributes.end ()) {
181 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
182 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
190 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
192 auto ref_parser = new xmlpp::DomParser ();
193 ref_parser->parse_memory (ref);
194 auto ref_root = ref_parser->get_document()->get_root_node ();
195 auto test_parser = new xmlpp::DomParser ();
196 test_parser->parse_memory (test);
197 auto test_root = test_parser->get_document()->get_root_node ();
199 check_xml (ref_root, test_root, ignore, ignore_whitespace);
203 check_file (boost::filesystem::path ref, boost::filesystem::path check)
205 uintmax_t size = boost::filesystem::file_size (ref);
206 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
207 auto ref_file = dcp::fopen_boost (ref, "rb");
208 BOOST_REQUIRE (ref_file);
209 auto check_file = dcp::fopen_boost (check, "rb");
210 BOOST_REQUIRE (check_file);
212 int const buffer_size = 65536;
213 auto ref_buffer = new uint8_t[buffer_size];
214 auto check_buffer = new uint8_t[buffer_size];
219 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
220 size_t r = fread (ref_buffer, 1, this_time, ref_file);
221 BOOST_CHECK_EQUAL (r, this_time);
222 r = fread (check_buffer, 1, this_time, check_file);
223 BOOST_CHECK_EQUAL (r, this_time);
225 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
226 for (int i = 0; i < buffer_size; ++i) {
227 if (ref_buffer[i] != check_buffer[i]) {
228 BOOST_CHECK_MESSAGE (
230 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
242 delete[] check_buffer;
249 RNGFixer::RNGFixer ()
251 Kumu::cth_test = true;
252 Kumu::FortunaRNG().Reset();
256 RNGFixer::~RNGFixer ()
258 Kumu::cth_test = false;
262 shared_ptr<dcp::MonoPictureAsset>
263 simple_picture (boost::filesystem::path path, string suffix, int frames)
265 dcp::MXFMetadata mxf_meta;
266 mxf_meta.company_name = "OpenDCP";
267 mxf_meta.product_name = "OpenDCP";
268 mxf_meta.product_version = "0.0.25";
270 shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
271 mp->set_metadata (mxf_meta);
272 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
274 dcp::Size const size (1998, 1080);
275 auto image = make_shared<dcp::OpenJPEGImage>(size);
276 for (int i = 0; i < 3; ++i) {
277 memset (image->data(i), 0, 2 * size.width * size.height);
279 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
281 for (int i = 0; i < frames; ++i) {
282 picture_writer->write (j2c.data(), j2c.size());
284 picture_writer->finalize ();
290 shared_ptr<dcp::SoundAsset>
291 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
293 int const channels = 6;
295 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
296 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
297 ms->_language = language;
298 ms->set_metadata (mxf_meta);
299 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
301 int const samples_per_frame = sample_rate / 24;
303 float* silence[channels];
304 for (auto i = 0; i < channels; ++i) {
305 silence[i] = new float[samples_per_frame];
306 memset (silence[i], 0, samples_per_frame * sizeof(float));
309 for (auto i = 0; i < frames; ++i) {
310 sound_writer->write (silence, samples_per_frame);
313 sound_writer->finalize ();
315 for (auto i = 0; i < channels; ++i) {
324 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard)
326 /* Some known metadata */
327 dcp::MXFMetadata mxf_meta;
328 mxf_meta.company_name = "OpenDCP";
329 mxf_meta.product_name = "OpenDCP";
330 mxf_meta.product_version = "0.0.25";
332 boost::filesystem::remove_all (path);
333 boost::filesystem::create_directories (path);
334 auto d = make_shared<dcp::DCP>(path);
335 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
336 cpl->set_annotation_text ("A Test DCP");
337 cpl->set_issuer ("OpenDCP 0.0.25");
338 cpl->set_creator ("OpenDCP 0.0.25");
339 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
340 cpl->set_content_version (
341 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
343 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
344 cpl->set_main_sound_sample_rate(48000);
345 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
346 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
347 cpl->set_version_number(1);
349 for (int i = 0; i < reels; ++i) {
350 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
352 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
353 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
355 auto reel = make_shared<dcp::Reel>(
356 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
357 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
360 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
362 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
364 if (i == reels - 1) {
365 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
377 shared_ptr<dcp::Subtitle>
380 return make_shared<dcp::SubtitleString>(
385 dcp::Colour(255, 255, 255),
388 dcp::Time(0, 0, 4, 0, 24),
389 dcp::Time(0, 0, 8, 0, 24),
397 dcp::Colour(255, 255, 255),
404 shared_ptr<dcp::ReelMarkersAsset>
405 simple_markers (int frames)
407 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
408 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
409 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
415 make_simple_with_interop_subs (boost::filesystem::path path)
417 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
419 auto subs = make_shared<dcp::InteropSubtitleAsset>();
420 subs->add (simple_subtitle());
422 boost::filesystem::create_directory (path / "subs");
423 dcp::ArrayData data(4096);
424 subs->add_font ("afont", data);
425 subs->write (path / "subs" / "subs.xml");
427 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
428 dcp->cpls().front()->reels().front()->add (reel_subs);
435 make_simple_with_smpte_subs (boost::filesystem::path path)
437 auto dcp = make_simple (path, 1, 192);
439 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
440 subs->set_language (dcp::LanguageTag("de-DE"));
441 subs->set_start_time (dcp::Time());
442 subs->add (simple_subtitle());
444 subs->write (path / "subs.mxf");
446 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
447 dcp->cpls().front()->reels().front()->add (reel_subs);
454 make_simple_with_interop_ccaps (boost::filesystem::path path)
456 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
458 auto subs = make_shared<dcp::InteropSubtitleAsset>();
459 subs->add (simple_subtitle());
460 subs->write (path / "ccap.xml");
462 auto reel_caps = make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
463 dcp->cpls().front()->reels().front()->add (reel_caps);
470 make_simple_with_smpte_ccaps (boost::filesystem::path path)
472 auto dcp = make_simple (path, 1, 192);
474 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
475 subs->set_language (dcp::LanguageTag("de-DE"));
476 subs->set_start_time (dcp::Time());
477 subs->add (simple_subtitle());
478 subs->write (path / "ccap.mxf");
480 auto reel_caps = make_shared<dcp::ReelClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
481 dcp->cpls()[0]->reels()[0]->add(reel_caps);
487 shared_ptr<dcp::OpenJPEGImage>
488 black_image (dcp::Size size)
490 auto image = make_shared<dcp::OpenJPEGImage>(size);
491 int const pixels = size.width * size.height;
492 for (int i = 0; i < 3; ++i) {
493 memset (image->data(i), 0, pixels * sizeof(int));
499 shared_ptr<dcp::ReelAsset>
500 black_picture_asset (boost::filesystem::path dir, int frames)
502 auto image = black_image ();
503 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
504 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
506 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
507 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
508 boost::filesystem::create_directories (dir);
509 auto writer = asset->start_write (dir / "pic.mxf", true);
510 for (int i = 0; i < frames; ++i) {
511 writer->write (frame.data(), frame.size());
515 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
519 boost::filesystem::path
520 find_file (boost::filesystem::path dir, string filename_part)
522 boost::optional<boost::filesystem::path> found;
523 for (auto i: boost::filesystem::directory_iterator(dir)) {
524 if (i.path().filename().string().find(filename_part) != string::npos) {
525 BOOST_REQUIRE (!found);
529 BOOST_REQUIRE (found);
534 BOOST_GLOBAL_FIXTURE (TestConfig);