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), dcp::PictureAsset::Behaviour::MAKE_NEW);
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, int channels)
297 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
298 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
302 ms->_language = language;
303 ms->set_metadata (mxf_meta);
304 auto sound_writer = ms->start_write(path / dcp::String::compose("audio%1.mxf", suffix), {}, dcp::SoundAsset::AtmosSync::DISABLED, dcp::SoundAsset::MCASubDescriptors::ENABLED);
306 int const samples_per_frame = sample_rate / 24;
308 float* silence[channels];
309 for (auto i = 0; i < channels; ++i) {
310 silence[i] = new float[samples_per_frame];
311 memset (silence[i], 0, samples_per_frame * sizeof(float));
314 for (auto i = 0; i < frames; ++i) {
315 sound_writer->write(silence, channels, samples_per_frame);
318 sound_writer->finalize ();
320 for (auto i = 0; i < channels; ++i) {
329 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
331 /* Some known metadata */
332 dcp::MXFMetadata mxf_meta;
333 mxf_meta.company_name = "OpenDCP";
334 mxf_meta.product_name = "OpenDCP";
335 mxf_meta.product_version = "0.0.25";
337 auto constexpr sample_rate = 48000;
339 boost::filesystem::remove_all (path);
340 boost::filesystem::create_directories (path);
341 auto d = make_shared<dcp::DCP>(path);
342 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
343 cpl->set_annotation_text ("A Test DCP");
344 cpl->set_issuer ("OpenDCP 0.0.25");
345 cpl->set_creator ("OpenDCP 0.0.25");
346 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
347 cpl->set_content_version (
348 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
350 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
351 cpl->set_main_sound_sample_rate(sample_rate);
352 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
353 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
354 cpl->set_version_number(1);
356 for (int i = 0; i < reels; ++i) {
357 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
359 auto mp = simple_picture (path, suffix, frames, key);
360 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
362 auto reel = make_shared<dcp::Reel>(
363 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
364 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
367 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
369 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
371 if (i == reels - 1) {
372 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
384 shared_ptr<dcp::Subtitle>
387 return std::make_shared<dcp::SubtitleString>(
392 dcp::Colour(255, 255, 255),
395 dcp::Time(0, 0, 4, 0, 24),
396 dcp::Time(0, 0, 8, 0, 24),
405 dcp::Colour(255, 255, 255),
413 shared_ptr<dcp::ReelMarkersAsset>
414 simple_markers (int frames)
416 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
417 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
418 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
424 make_simple_with_interop_subs (boost::filesystem::path path)
426 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
428 auto subs = make_shared<dcp::InteropSubtitleAsset>();
429 subs->add (simple_subtitle());
431 boost::filesystem::create_directory (path / "subs");
432 dcp::ArrayData data(4096);
433 memset(data.data(), 0, data.size());
434 subs->add_font ("afont", data);
435 subs->write (path / "subs" / "subs.xml");
437 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
438 dcp->cpls().front()->reels().front()->add (reel_subs);
445 make_simple_with_smpte_subs (boost::filesystem::path path)
447 auto dcp = make_simple (path, 1, 192);
449 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
450 subs->set_language (dcp::LanguageTag("de-DE"));
451 subs->set_start_time (dcp::Time());
452 subs->add (simple_subtitle());
454 subs->write (path / "subs.mxf");
456 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
457 dcp->cpls().front()->reels().front()->add (reel_subs);
464 make_simple_with_interop_ccaps (boost::filesystem::path path)
466 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
468 auto subs = make_shared<dcp::InteropSubtitleAsset>();
469 subs->add (simple_subtitle());
470 subs->write (path / "ccap.xml");
472 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
473 dcp->cpls()[0]->reels()[0]->add (reel_caps);
480 make_simple_with_smpte_ccaps (boost::filesystem::path path)
482 auto dcp = make_simple (path, 1, 192);
484 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
485 subs->set_language (dcp::LanguageTag("de-DE"));
486 subs->set_start_time (dcp::Time());
487 subs->add (simple_subtitle());
488 subs->write (path / "ccap.mxf");
490 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
491 dcp->cpls()[0]->reels()[0]->add(reel_caps);
497 shared_ptr<dcp::OpenJPEGImage>
498 black_image (dcp::Size size)
500 auto image = make_shared<dcp::OpenJPEGImage>(size);
501 int const pixels = size.width * size.height;
502 for (int i = 0; i < 3; ++i) {
503 memset (image->data(i), 0, pixels * sizeof(int));
509 shared_ptr<dcp::ReelAsset>
510 black_picture_asset (boost::filesystem::path dir, int frames)
512 auto image = black_image ();
513 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
514 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
516 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
517 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
518 boost::filesystem::create_directories (dir);
519 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
520 for (int i = 0; i < frames; ++i) {
521 writer->write (frame.data(), frame.size());
525 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
529 boost::filesystem::path
530 find_file (boost::filesystem::path dir, string filename_part)
532 boost::optional<boost::filesystem::path> found;
533 for (auto i: boost::filesystem::directory_iterator(dir)) {
534 if (i.path().filename().string().find(filename_part) != string::npos) {
535 BOOST_REQUIRE (!found);
539 BOOST_REQUIRE (found);
544 BOOST_GLOBAL_FIXTURE (TestConfig);