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 auto ref_buffer = new uint8_t[buffer_size];
221 auto check_buffer = new uint8_t[buffer_size];
226 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
227 size_t r = ref_file.read(ref_buffer, 1, this_time);
228 BOOST_CHECK_EQUAL (r, this_time);
229 r = check_file.read(check_buffer, 1, this_time);
230 BOOST_CHECK_EQUAL (r, this_time);
232 if (memcmp(ref_buffer, check_buffer, 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)
249 delete[] check_buffer;
253 RNGFixer::RNGFixer ()
255 Kumu::cth_test = true;
256 Kumu::FortunaRNG().Reset();
260 RNGFixer::~RNGFixer ()
262 Kumu::cth_test = false;
266 shared_ptr<dcp::MonoPictureAsset>
267 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
269 dcp::MXFMetadata mxf_meta;
270 mxf_meta.company_name = "OpenDCP";
271 mxf_meta.product_name = "OpenDCP";
272 mxf_meta.product_version = "0.0.25";
274 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
275 mp->set_metadata (mxf_meta);
279 auto picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
281 dcp::Size const size (1998, 1080);
282 auto image = make_shared<dcp::OpenJPEGImage>(size);
283 for (int i = 0; i < 3; ++i) {
284 memset (image->data(i), 0, 2 * size.width * size.height);
286 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
288 for (int i = 0; i < frames; ++i) {
289 picture_writer->write (j2c.data(), j2c.size());
291 picture_writer->finalize ();
297 shared_ptr<dcp::SoundAsset>
298 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate, optional<dcp::Key> key)
300 int const channels = 6;
302 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
303 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
307 ms->_language = language;
308 ms->set_metadata (mxf_meta);
309 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
311 int const samples_per_frame = sample_rate / 24;
313 float* silence[channels];
314 for (auto i = 0; i < channels; ++i) {
315 silence[i] = new float[samples_per_frame];
316 memset (silence[i], 0, samples_per_frame * sizeof(float));
319 for (auto i = 0; i < frames; ++i) {
320 sound_writer->write (silence, samples_per_frame);
323 sound_writer->finalize ();
325 for (auto i = 0; i < channels; ++i) {
334 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
336 /* Some known metadata */
337 dcp::MXFMetadata mxf_meta;
338 mxf_meta.company_name = "OpenDCP";
339 mxf_meta.product_name = "OpenDCP";
340 mxf_meta.product_version = "0.0.25";
342 auto constexpr sample_rate = 48000;
344 boost::filesystem::remove_all (path);
345 boost::filesystem::create_directories (path);
346 auto d = make_shared<dcp::DCP>(path);
347 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
348 cpl->set_annotation_text ("A Test DCP");
349 cpl->set_issuer ("OpenDCP 0.0.25");
350 cpl->set_creator ("OpenDCP 0.0.25");
351 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
352 cpl->set_content_version (
353 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
355 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
356 cpl->set_main_sound_sample_rate(sample_rate);
357 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
358 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
359 cpl->set_version_number(1);
361 for (int i = 0; i < reels; ++i) {
362 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
364 auto mp = simple_picture (path, suffix, frames, key);
365 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
367 auto reel = make_shared<dcp::Reel>(
368 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
369 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
372 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
374 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
376 if (i == reels - 1) {
377 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
389 shared_ptr<dcp::Subtitle>
392 return make_shared<dcp::SubtitleString>(
397 dcp::Colour(255, 255, 255),
400 dcp::Time(0, 0, 4, 0, 24),
401 dcp::Time(0, 0, 8, 0, 24),
409 dcp::Colour(255, 255, 255),
417 shared_ptr<dcp::ReelMarkersAsset>
418 simple_markers (int frames)
420 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
421 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
422 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
428 make_simple_with_interop_subs (boost::filesystem::path path)
430 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
432 auto subs = make_shared<dcp::InteropSubtitleAsset>();
433 subs->add (simple_subtitle());
435 boost::filesystem::create_directory (path / "subs");
436 dcp::ArrayData data(4096);
437 subs->add_font ("afont", data);
438 subs->write (path / "subs" / "subs.xml");
440 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
441 dcp->cpls().front()->reels().front()->add (reel_subs);
448 make_simple_with_smpte_subs (boost::filesystem::path path)
450 auto dcp = make_simple (path, 1, 192);
452 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
453 subs->set_language (dcp::LanguageTag("de-DE"));
454 subs->set_start_time (dcp::Time());
455 subs->add (simple_subtitle());
457 subs->write (path / "subs.mxf");
459 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
460 dcp->cpls().front()->reels().front()->add (reel_subs);
467 make_simple_with_interop_ccaps (boost::filesystem::path path)
469 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
471 auto subs = make_shared<dcp::InteropSubtitleAsset>();
472 subs->add (simple_subtitle());
473 subs->write (path / "ccap.xml");
475 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
476 dcp->cpls()[0]->reels()[0]->add (reel_caps);
483 make_simple_with_smpte_ccaps (boost::filesystem::path path)
485 auto dcp = make_simple (path, 1, 192);
487 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
488 subs->set_language (dcp::LanguageTag("de-DE"));
489 subs->set_start_time (dcp::Time());
490 subs->add (simple_subtitle());
491 subs->write (path / "ccap.mxf");
493 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
494 dcp->cpls()[0]->reels()[0]->add(reel_caps);
500 shared_ptr<dcp::OpenJPEGImage>
501 black_image (dcp::Size size)
503 auto image = make_shared<dcp::OpenJPEGImage>(size);
504 int const pixels = size.width * size.height;
505 for (int i = 0; i < 3; ++i) {
506 memset (image->data(i), 0, pixels * sizeof(int));
512 shared_ptr<dcp::ReelAsset>
513 black_picture_asset (boost::filesystem::path dir, int frames)
515 auto image = black_image ();
516 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
517 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
519 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
520 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
521 boost::filesystem::create_directories (dir);
522 auto writer = asset->start_write (dir / "pic.mxf", true);
523 for (int i = 0; i < frames; ++i) {
524 writer->write (frame.data(), frame.size());
528 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
532 boost::filesystem::path
533 find_file (boost::filesystem::path dir, string filename_part)
535 boost::optional<boost::filesystem::path> found;
536 for (auto i: boost::filesystem::directory_iterator(dir)) {
537 if (i.path().filename().string().find(filename_part) != string::npos) {
538 BOOST_REQUIRE (!found);
542 BOOST_REQUIRE (found);
547 BOOST_GLOBAL_FIXTURE (TestConfig);