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_interop_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_closed_caption_asset.h"
54 #include "reel_smpte_subtitle_asset.h"
55 #include "reel_sound_asset.h"
56 #include "smpte_subtitle_asset.h"
57 #include "sound_asset.h"
58 #include "sound_asset_writer.h"
62 LIBDCP_DISABLE_WARNINGS
63 #include <asdcp/KM_util.h>
64 #include <asdcp/KM_prng.h>
65 LIBDCP_ENABLE_WARNINGS
67 LIBDCP_DISABLE_WARNINGS
68 #include <libxml++/libxml++.h>
69 LIBDCP_ENABLE_WARNINGS
70 #include <boost/test/unit_test.hpp>
78 using std::shared_ptr;
79 using std::make_shared;
80 using boost::optional;
83 boost::filesystem::path private_test;
84 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
92 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
93 private_test = boost::unit_test::framework::master_test_suite().argv[1];
96 using namespace boost::filesystem;
97 boost::system::error_code ec;
98 remove_all (xsd_test, ec);
99 boost::filesystem::create_directory (xsd_test);
100 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
101 copy_file (*i, xsd_test / i->path().filename());
108 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
110 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
111 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
113 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
117 auto whitespace_content = [](xmlpp::Node* node) {
118 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
119 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
122 auto ref_children = ref->get_children ();
123 auto test_children = test->get_children ();
125 auto k = ref_children.begin ();
126 auto l = test_children.begin ();
127 while (k != ref_children.end() && l != test_children.end()) {
129 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
134 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
139 if (whitespace_content(*k) && ignore_whitespace) {
144 if (whitespace_content(*l) && ignore_whitespace) {
149 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
151 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
152 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
153 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
154 if (ref_el && test_el) {
155 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
158 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
159 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
160 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
161 if (ref_cn && test_cn) {
162 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
169 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
173 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
177 BOOST_REQUIRE (k == ref_children.end());
178 BOOST_REQUIRE (l == test_children.end());
180 auto ref_attributes = ref->get_attributes ();
181 auto test_attributes = test->get_attributes ();
182 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
184 auto m = ref_attributes.begin();
185 auto n = test_attributes.begin();
186 while (m != ref_attributes.end ()) {
187 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
188 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
196 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
198 auto ref_parser = new xmlpp::DomParser ();
199 ref_parser->parse_memory (ref);
200 auto ref_root = ref_parser->get_document()->get_root_node ();
201 auto test_parser = new xmlpp::DomParser ();
202 test_parser->parse_memory (test);
203 auto test_root = test_parser->get_document()->get_root_node ();
205 check_xml (ref_root, test_root, ignore, ignore_whitespace);
209 check_file (boost::filesystem::path ref, boost::filesystem::path check)
211 uintmax_t size = boost::filesystem::file_size (ref);
212 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
213 auto ref_file = dcp::fopen_boost (ref, "rb");
214 BOOST_REQUIRE (ref_file);
215 auto check_file = dcp::fopen_boost (check, "rb");
216 BOOST_REQUIRE (check_file);
218 int const buffer_size = 65536;
219 auto ref_buffer = new uint8_t[buffer_size];
220 auto check_buffer = new uint8_t[buffer_size];
225 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
226 size_t r = fread (ref_buffer, 1, this_time, ref_file);
227 BOOST_CHECK_EQUAL (r, this_time);
228 r = fread (check_buffer, 1, this_time, check_file);
229 BOOST_CHECK_EQUAL (r, this_time);
231 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
232 for (int i = 0; i < buffer_size; ++i) {
233 if (ref_buffer[i] != check_buffer[i]) {
234 BOOST_CHECK_MESSAGE (
236 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
248 delete[] check_buffer;
255 RNGFixer::RNGFixer ()
257 Kumu::cth_test = true;
258 Kumu::FortunaRNG().Reset();
262 RNGFixer::~RNGFixer ()
264 Kumu::cth_test = false;
268 shared_ptr<dcp::MonoPictureAsset>
269 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
271 dcp::MXFMetadata mxf_meta;
272 mxf_meta.company_name = "OpenDCP";
273 mxf_meta.product_name = "OpenDCP";
274 mxf_meta.product_version = "0.0.25";
276 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
277 mp->set_metadata (mxf_meta);
281 auto picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
283 dcp::Size const size (1998, 1080);
284 auto image = make_shared<dcp::OpenJPEGImage>(size);
285 for (int i = 0; i < 3; ++i) {
286 memset (image->data(i), 0, 2 * size.width * size.height);
288 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
290 for (int i = 0; i < frames; ++i) {
291 picture_writer->write (j2c.data(), j2c.size());
293 picture_writer->finalize ();
299 shared_ptr<dcp::SoundAsset>
300 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate, optional<dcp::Key> key)
302 int const channels = 6;
304 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
305 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
309 ms->_language = language;
310 ms->set_metadata (mxf_meta);
311 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
313 int const samples_per_frame = sample_rate / 24;
315 float* silence[channels];
316 for (auto i = 0; i < channels; ++i) {
317 silence[i] = new float[samples_per_frame];
318 memset (silence[i], 0, samples_per_frame * sizeof(float));
321 for (auto i = 0; i < frames; ++i) {
322 sound_writer->write (silence, samples_per_frame);
325 sound_writer->finalize ();
327 for (auto i = 0; i < channels; ++i) {
336 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
338 /* Some known metadata */
339 dcp::MXFMetadata mxf_meta;
340 mxf_meta.company_name = "OpenDCP";
341 mxf_meta.product_name = "OpenDCP";
342 mxf_meta.product_version = "0.0.25";
344 auto constexpr sample_rate = 48000;
346 boost::filesystem::remove_all (path);
347 boost::filesystem::create_directories (path);
348 auto d = make_shared<dcp::DCP>(path);
349 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
350 cpl->set_annotation_text ("A Test DCP");
351 cpl->set_issuer ("OpenDCP 0.0.25");
352 cpl->set_creator ("OpenDCP 0.0.25");
353 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
354 cpl->set_content_version (
355 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
357 cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
358 cpl->set_main_sound_sample_rate(sample_rate);
359 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
360 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
361 cpl->set_version_number(1);
363 for (int i = 0; i < reels; ++i) {
364 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
366 auto mp = simple_picture (path, suffix, frames, key);
367 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
369 auto reel = make_shared<dcp::Reel>(
370 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
371 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
374 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
376 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
378 if (i == reels - 1) {
379 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
391 shared_ptr<dcp::Subtitle>
394 return make_shared<dcp::SubtitleString>(
399 dcp::Colour(255, 255, 255),
402 dcp::Time(0, 0, 4, 0, 24),
403 dcp::Time(0, 0, 8, 0, 24),
411 dcp::Colour(255, 255, 255),
418 shared_ptr<dcp::ReelMarkersAsset>
419 simple_markers (int frames)
421 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
422 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
423 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
429 make_simple_with_interop_subs (boost::filesystem::path path)
431 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
433 auto subs = make_shared<dcp::InteropSubtitleAsset>();
434 subs->add (simple_subtitle());
436 boost::filesystem::create_directory (path / "subs");
437 dcp::ArrayData data(4096);
438 subs->add_font ("afont", data);
439 subs->write (path / "subs" / "subs.xml");
441 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
442 dcp->cpls().front()->reels().front()->add (reel_subs);
449 make_simple_with_smpte_subs (boost::filesystem::path path)
451 auto dcp = make_simple (path, 1, 192);
453 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
454 subs->set_language (dcp::LanguageTag("de-DE"));
455 subs->set_start_time (dcp::Time());
456 subs->add (simple_subtitle());
458 subs->write (path / "subs.mxf");
460 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
461 dcp->cpls().front()->reels().front()->add (reel_subs);
468 make_simple_with_interop_ccaps (boost::filesystem::path path)
470 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
472 auto subs = make_shared<dcp::InteropSubtitleAsset>();
473 subs->add (simple_subtitle());
474 subs->write (path / "ccap.xml");
476 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
477 dcp->cpls()[0]->reels()[0]->add (reel_caps);
484 make_simple_with_smpte_ccaps (boost::filesystem::path path)
486 auto dcp = make_simple (path, 1, 192);
488 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
489 subs->set_language (dcp::LanguageTag("de-DE"));
490 subs->set_start_time (dcp::Time());
491 subs->add (simple_subtitle());
492 subs->write (path / "ccap.mxf");
494 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
495 dcp->cpls()[0]->reels()[0]->add(reel_caps);
501 shared_ptr<dcp::OpenJPEGImage>
502 black_image (dcp::Size size)
504 auto image = make_shared<dcp::OpenJPEGImage>(size);
505 int const pixels = size.width * size.height;
506 for (int i = 0; i < 3; ++i) {
507 memset (image->data(i), 0, pixels * sizeof(int));
513 shared_ptr<dcp::ReelAsset>
514 black_picture_asset (boost::filesystem::path dir, int frames)
516 auto image = black_image ();
517 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
518 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
520 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
521 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
522 boost::filesystem::create_directories (dir);
523 auto writer = asset->start_write (dir / "pic.mxf", true);
524 for (int i = 0; i < frames; ++i) {
525 writer->write (frame.data(), frame.size());
529 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
533 boost::filesystem::path
534 find_file (boost::filesystem::path dir, string filename_part)
536 boost::optional<boost::filesystem::path> found;
537 for (auto i: boost::filesystem::directory_iterator(dir)) {
538 if (i.path().filename().string().find(filename_part) != string::npos) {
539 BOOST_REQUIRE (!found);
543 BOOST_REQUIRE (found);
548 BOOST_GLOBAL_FIXTURE (TestConfig);