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"
41 #include "j2k_transcode.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_asset.h"
44 #include "openjpeg_image.h"
45 #include "picture_asset_writer.h"
46 #include "picture_asset_writer.h"
48 #include "reel_asset.h"
49 #include "reel_interop_closed_caption_asset.h"
50 #include "reel_interop_subtitle_asset.h"
51 #include "reel_markers_asset.h"
52 #include "reel_mono_picture_asset.h"
53 #include "reel_mono_picture_asset.h"
54 #include "reel_smpte_closed_caption_asset.h"
55 #include "reel_smpte_subtitle_asset.h"
56 #include "reel_sound_asset.h"
57 #include "smpte_subtitle_asset.h"
58 #include "sound_asset.h"
59 #include "sound_asset_writer.h"
63 LIBDCP_DISABLE_WARNINGS
64 #include <asdcp/KM_util.h>
65 #include <asdcp/KM_prng.h>
66 LIBDCP_ENABLE_WARNINGS
68 LIBDCP_DISABLE_WARNINGS
69 #include <libxml++/libxml++.h>
70 LIBDCP_ENABLE_WARNINGS
71 #include <boost/test/unit_test.hpp>
79 using std::shared_ptr;
80 using std::make_shared;
81 using boost::optional;
84 boost::filesystem::path private_test;
85 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
93 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
94 private_test = boost::unit_test::framework::master_test_suite().argv[1];
97 using namespace boost::filesystem;
98 boost::system::error_code ec;
99 remove_all (xsd_test, ec);
100 boost::filesystem::create_directory (xsd_test);
101 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
102 copy_file (*i, xsd_test / i->path().filename());
109 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
111 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
112 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
114 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
118 auto whitespace_content = [](xmlpp::Node* node) {
119 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
120 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
123 auto ref_children = ref->get_children ();
124 auto test_children = test->get_children ();
126 auto k = ref_children.begin ();
127 auto l = test_children.begin ();
128 while (k != ref_children.end() && l != test_children.end()) {
130 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
135 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
140 if (whitespace_content(*k) && ignore_whitespace) {
145 if (whitespace_content(*l) && ignore_whitespace) {
150 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
152 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
153 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
154 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
155 if (ref_el && test_el) {
156 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
159 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
160 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
161 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
162 if (ref_cn && test_cn) {
163 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
170 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
174 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
178 BOOST_REQUIRE (k == ref_children.end());
179 BOOST_REQUIRE (l == test_children.end());
181 auto ref_attributes = ref->get_attributes ();
182 auto test_attributes = test->get_attributes ();
183 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
185 auto m = ref_attributes.begin();
186 auto n = test_attributes.begin();
187 while (m != ref_attributes.end ()) {
188 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
189 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
197 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
199 auto ref_parser = new xmlpp::DomParser ();
200 ref_parser->parse_memory (ref);
201 auto ref_root = ref_parser->get_document()->get_root_node ();
202 auto test_parser = new xmlpp::DomParser ();
203 test_parser->parse_memory (test);
204 auto test_root = test_parser->get_document()->get_root_node ();
206 check_xml (ref_root, test_root, ignore, ignore_whitespace);
210 check_file (boost::filesystem::path ref, boost::filesystem::path check)
212 uintmax_t size = boost::filesystem::file_size (ref);
213 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
214 dcp::File ref_file(ref, "rb");
215 BOOST_REQUIRE (ref_file);
216 dcp::File check_file(check, "rb");
217 BOOST_REQUIRE (check_file);
219 int const buffer_size = 65536;
220 std::vector<uint8_t> ref_buffer(buffer_size);
221 std::vector<uint8_t> check_buffer(buffer_size);
226 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
227 size_t r = ref_file.read(ref_buffer.data(), 1, this_time);
228 BOOST_CHECK_EQUAL (r, this_time);
229 r = check_file.read(check_buffer.data(), 1, this_time);
230 BOOST_CHECK_EQUAL (r, this_time);
232 if (memcmp(ref_buffer.data(), check_buffer.data(), this_time) != 0) {
233 for (int i = 0; i < buffer_size; ++i) {
234 if (ref_buffer[i] != check_buffer[i]) {
235 BOOST_CHECK_MESSAGE (
237 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
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, optional<dcp::Key> key)
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 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
272 mp->set_metadata (mxf_meta);
276 auto picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
278 dcp::Size const size (1998, 1080);
279 auto image = make_shared<dcp::OpenJPEGImage>(size);
280 for (int i = 0; i < 3; ++i) {
281 memset (image->data(i), 0, 2 * size.width * size.height);
283 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
285 for (int i = 0; i < frames; ++i) {
286 picture_writer->write (j2c.data(), j2c.size());
288 picture_writer->finalize ();
294 shared_ptr<dcp::SoundAsset>
295 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate, optional<dcp::Key> key)
297 int const channels = 6;
299 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
300 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
304 ms->_language = language;
305 ms->set_metadata (mxf_meta);
306 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
308 int const samples_per_frame = sample_rate / 24;
310 float* silence[channels];
311 for (auto i = 0; i < channels; ++i) {
312 silence[i] = new float[samples_per_frame];
313 memset (silence[i], 0, samples_per_frame * sizeof(float));
316 for (auto i = 0; i < frames; ++i) {
317 sound_writer->write (silence, samples_per_frame);
320 sound_writer->finalize ();
322 for (auto i = 0; i < channels; ++i) {
331 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
333 /* Some known metadata */
334 dcp::MXFMetadata mxf_meta;
335 mxf_meta.company_name = "OpenDCP";
336 mxf_meta.product_name = "OpenDCP";
337 mxf_meta.product_version = "0.0.25";
339 auto constexpr sample_rate = 48000;
341 boost::filesystem::remove_all (path);
342 boost::filesystem::create_directories (path);
343 auto d = make_shared<dcp::DCP>(path);
344 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
345 cpl->set_annotation_text ("A Test DCP");
346 cpl->set_issuer ("OpenDCP 0.0.25");
347 cpl->set_creator ("OpenDCP 0.0.25");
348 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
349 cpl->set_content_version (
350 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
352 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
353 cpl->set_main_sound_sample_rate(sample_rate);
354 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
355 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
356 cpl->set_version_number(1);
358 for (int i = 0; i < reels; ++i) {
359 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
361 auto mp = simple_picture (path, suffix, frames, key);
362 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
364 auto reel = make_shared<dcp::Reel>(
365 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
366 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
369 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
371 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
373 if (i == reels - 1) {
374 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
386 shared_ptr<dcp::Subtitle>
389 return std::make_shared<dcp::SubtitleString>(
394 dcp::Colour(255, 255, 255),
397 dcp::Time(0, 0, 4, 0, 24),
398 dcp::Time(0, 0, 8, 0, 24),
407 dcp::Colour(255, 255, 255),
415 shared_ptr<dcp::ReelMarkersAsset>
416 simple_markers (int frames)
418 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
419 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
420 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
426 make_simple_with_interop_subs (boost::filesystem::path path)
428 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
430 auto subs = make_shared<dcp::InteropSubtitleAsset>();
431 subs->add (simple_subtitle());
433 boost::filesystem::create_directory (path / "subs");
434 dcp::ArrayData data(4096);
435 memset(data.data(), 0, data.size());
436 subs->add_font ("afont", data);
437 subs->write (path / "subs" / "subs.xml");
439 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
440 dcp->cpls().front()->reels().front()->add (reel_subs);
447 make_simple_with_smpte_subs (boost::filesystem::path path)
449 auto dcp = make_simple (path, 1, 192);
451 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
452 subs->set_language (dcp::LanguageTag("de-DE"));
453 subs->set_start_time (dcp::Time());
454 subs->add (simple_subtitle());
456 subs->write (path / "subs.mxf");
458 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
459 dcp->cpls().front()->reels().front()->add (reel_subs);
466 make_simple_with_interop_ccaps (boost::filesystem::path path)
468 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
470 auto subs = make_shared<dcp::InteropSubtitleAsset>();
471 subs->add (simple_subtitle());
472 subs->write (path / "ccap.xml");
474 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
475 dcp->cpls()[0]->reels()[0]->add (reel_caps);
482 make_simple_with_smpte_ccaps (boost::filesystem::path path)
484 auto dcp = make_simple (path, 1, 192);
486 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
487 subs->set_language (dcp::LanguageTag("de-DE"));
488 subs->set_start_time (dcp::Time());
489 subs->add (simple_subtitle());
490 subs->write (path / "ccap.mxf");
492 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
493 dcp->cpls()[0]->reels()[0]->add(reel_caps);
499 shared_ptr<dcp::OpenJPEGImage>
500 black_image (dcp::Size size)
502 auto image = make_shared<dcp::OpenJPEGImage>(size);
503 int const pixels = size.width * size.height;
504 for (int i = 0; i < 3; ++i) {
505 memset (image->data(i), 0, pixels * sizeof(int));
511 shared_ptr<dcp::ReelAsset>
512 black_picture_asset (boost::filesystem::path dir, int frames)
514 auto image = black_image ();
515 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
516 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
518 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
519 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
520 boost::filesystem::create_directories (dir);
521 auto writer = asset->start_write (dir / "pic.mxf", true);
522 for (int i = 0; i < frames; ++i) {
523 writer->write (frame.data(), frame.size());
527 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
531 boost::filesystem::path
532 find_file (boost::filesystem::path dir, string filename_part)
534 boost::optional<boost::filesystem::path> found;
535 for (auto i: boost::filesystem::directory_iterator(dir)) {
536 if (i.path().filename().string().find(filename_part) != string::npos) {
537 BOOST_REQUIRE (!found);
541 BOOST_REQUIRE (found);
546 BOOST_GLOBAL_FIXTURE (TestConfig);