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/algorithm/string.hpp>
72 #include <boost/test/unit_test.hpp>
80 using std::shared_ptr;
81 using std::make_shared;
82 using boost::optional;
85 boost::filesystem::path private_test;
86 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
94 auto const argc = boost::unit_test::framework::master_test_suite().argc;
95 auto const argv = boost::unit_test::framework::master_test_suite().argv;
96 if (argc >= 3 && strcmp(argv[1], "--") == 0) {
97 /* For some reason on Ubuntu 16.04 the -- we pass in ends up in argv[1] so we
98 * need to get the private_test from argv[2]...
100 private_test = argv[2];
101 } else if (argc >= 2) {
102 /* ... but everywhere else it's removed, so we can just get the private_test
103 * value from argv[1].
105 private_test = argv[1];
108 using namespace boost::filesystem;
109 boost::system::error_code ec;
110 remove_all (xsd_test, ec);
111 boost::filesystem::create_directory (xsd_test);
112 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
113 copy_file (*i, xsd_test / i->path().filename());
120 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
122 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
123 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
125 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
129 auto whitespace_content = [](xmlpp::Node* node) {
130 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
131 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
134 auto ref_children = ref->get_children ();
135 auto test_children = test->get_children ();
137 auto k = ref_children.begin ();
138 auto l = test_children.begin ();
139 while (k != ref_children.end() && l != test_children.end()) {
141 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
146 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
151 if (whitespace_content(*k) && ignore_whitespace) {
156 if (whitespace_content(*l) && ignore_whitespace) {
161 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
163 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
164 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
165 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
166 if (ref_el && test_el) {
167 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
170 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
171 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
172 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
173 if (ref_cn && test_cn) {
174 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
181 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
185 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
189 BOOST_REQUIRE (k == ref_children.end());
190 BOOST_REQUIRE (l == test_children.end());
192 auto ref_attributes = ref->get_attributes ();
193 auto test_attributes = test->get_attributes ();
194 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
196 auto m = ref_attributes.begin();
197 auto n = test_attributes.begin();
198 while (m != ref_attributes.end ()) {
199 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
200 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
208 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
210 auto ref_parser = new xmlpp::DomParser ();
211 ref_parser->parse_memory (ref);
212 auto ref_root = ref_parser->get_document()->get_root_node ();
213 auto test_parser = new xmlpp::DomParser ();
214 test_parser->parse_memory (test);
215 auto test_root = test_parser->get_document()->get_root_node ();
217 check_xml (ref_root, test_root, ignore, ignore_whitespace);
221 check_file (boost::filesystem::path ref, boost::filesystem::path check)
223 uintmax_t size = boost::filesystem::file_size (ref);
224 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
225 dcp::File ref_file(ref, "rb");
226 BOOST_REQUIRE (ref_file);
227 dcp::File check_file(check, "rb");
228 BOOST_REQUIRE (check_file);
230 int const buffer_size = 65536;
231 std::vector<uint8_t> ref_buffer(buffer_size);
232 std::vector<uint8_t> check_buffer(buffer_size);
237 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
238 size_t r = ref_file.read(ref_buffer.data(), 1, this_time);
239 BOOST_CHECK_EQUAL (r, this_time);
240 r = check_file.read(check_buffer.data(), 1, this_time);
241 BOOST_CHECK_EQUAL (r, this_time);
243 if (memcmp(ref_buffer.data(), check_buffer.data(), this_time) != 0) {
244 for (int i = 0; i < buffer_size; ++i) {
245 if (ref_buffer[i] != check_buffer[i]) {
246 BOOST_CHECK_MESSAGE (
248 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
261 RNGFixer::RNGFixer ()
263 Kumu::cth_test = true;
264 Kumu::FortunaRNG().Reset();
268 RNGFixer::~RNGFixer ()
270 Kumu::cth_test = false;
274 shared_ptr<dcp::MonoPictureAsset>
275 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
277 dcp::MXFMetadata mxf_meta;
278 mxf_meta.company_name = "OpenDCP";
279 mxf_meta.product_name = "OpenDCP";
280 mxf_meta.product_version = "0.0.25";
282 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
283 mp->set_metadata (mxf_meta);
287 auto picture_writer = mp->start_write(path / dcp::String::compose("video%1.mxf", suffix), dcp::PictureAsset::Behaviour::MAKE_NEW);
289 dcp::Size const size (1998, 1080);
290 auto image = make_shared<dcp::OpenJPEGImage>(size);
291 for (int i = 0; i < 3; ++i) {
292 memset (image->data(i), 0, 2 * size.width * size.height);
294 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
296 for (int i = 0; i < frames; ++i) {
297 picture_writer->write (j2c.data(), j2c.size());
299 picture_writer->finalize ();
305 shared_ptr<dcp::SoundAsset>
306 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)
308 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
309 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
313 ms->_language = language;
314 ms->set_metadata (mxf_meta);
315 auto sound_writer = ms->start_write(path / dcp::String::compose("audio%1.mxf", suffix), {}, dcp::SoundAsset::AtmosSync::DISABLED, dcp::SoundAsset::MCASubDescriptors::ENABLED);
317 int const samples_per_frame = sample_rate / 24;
319 float* silence[channels];
320 for (auto i = 0; i < channels; ++i) {
321 silence[i] = new float[samples_per_frame];
322 memset (silence[i], 0, samples_per_frame * sizeof(float));
325 for (auto i = 0; i < frames; ++i) {
326 sound_writer->write(silence, channels, samples_per_frame);
329 sound_writer->finalize ();
331 for (auto i = 0; i < channels; ++i) {
340 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
342 /* Some known metadata */
343 dcp::MXFMetadata mxf_meta;
344 mxf_meta.company_name = "OpenDCP";
345 mxf_meta.product_name = "OpenDCP";
346 mxf_meta.product_version = "0.0.25";
348 auto constexpr sample_rate = 48000;
350 boost::filesystem::remove_all (path);
351 boost::filesystem::create_directories (path);
352 auto d = make_shared<dcp::DCP>(path);
353 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
354 cpl->set_annotation_text ("A Test DCP");
355 cpl->set_issuer ("OpenDCP 0.0.25");
356 cpl->set_creator ("OpenDCP 0.0.25");
357 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
358 cpl->set_content_version (
359 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
361 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
362 cpl->set_main_sound_sample_rate(sample_rate);
363 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
364 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
365 cpl->set_version_number(1);
367 for (int i = 0; i < reels; ++i) {
368 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
370 auto mp = simple_picture (path, suffix, frames, key);
371 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
373 auto reel = make_shared<dcp::Reel>(
374 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
375 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
378 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
380 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
382 if (i == reels - 1) {
383 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
390 d->set_annotation_text("A Test DCP");
396 shared_ptr<dcp::Subtitle>
399 return std::make_shared<dcp::SubtitleString>(
404 dcp::Colour(255, 255, 255),
407 dcp::Time(0, 0, 4, 0, 24),
408 dcp::Time(0, 0, 8, 0, 24),
417 dcp::Colour(255, 255, 255),
425 shared_ptr<dcp::ReelMarkersAsset>
426 simple_markers (int frames)
428 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
429 markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
430 markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
436 make_simple_with_interop_subs (boost::filesystem::path path)
438 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
440 auto subs = make_shared<dcp::InteropSubtitleAsset>();
441 subs->add (simple_subtitle());
443 boost::filesystem::create_directory (path / "subs");
444 dcp::ArrayData data(4096);
445 memset(data.data(), 0, data.size());
446 subs->add_font ("afont", data);
447 subs->write (path / "subs" / "subs.xml");
449 auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
450 dcp->cpls().front()->reels().front()->add (reel_subs);
457 make_simple_with_smpte_subs (boost::filesystem::path path)
459 auto dcp = make_simple (path, 1, 192);
461 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
462 subs->set_language (dcp::LanguageTag("de-DE"));
463 subs->set_start_time (dcp::Time());
464 subs->add (simple_subtitle());
465 dcp::ArrayData fake_font(1024);
466 subs->add_font("font", fake_font);
468 subs->write (path / "subs.mxf");
470 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
471 dcp->cpls().front()->reels().front()->add (reel_subs);
478 make_simple_with_interop_ccaps (boost::filesystem::path path)
480 auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
482 auto subs = make_shared<dcp::InteropSubtitleAsset>();
483 subs->add (simple_subtitle());
484 subs->write (path / "ccap.xml");
486 auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
487 dcp->cpls()[0]->reels()[0]->add (reel_caps);
494 make_simple_with_smpte_ccaps (boost::filesystem::path path)
496 auto dcp = make_simple (path, 1, 192);
498 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
499 subs->set_language (dcp::LanguageTag("de-DE"));
500 subs->set_start_time (dcp::Time());
501 subs->add (simple_subtitle());
502 dcp::ArrayData fake_font(1024);
503 subs->add_font("font", fake_font);
504 subs->write (path / "ccap.mxf");
506 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
507 dcp->cpls()[0]->reels()[0]->add(reel_caps);
513 shared_ptr<dcp::OpenJPEGImage>
514 black_image (dcp::Size size)
516 auto image = make_shared<dcp::OpenJPEGImage>(size);
517 int const pixels = size.width * size.height;
518 for (int i = 0; i < 3; ++i) {
519 memset (image->data(i), 0, pixels * sizeof(int));
525 shared_ptr<dcp::ReelAsset>
526 black_picture_asset (boost::filesystem::path dir, int frames)
528 auto image = black_image ();
529 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
530 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
532 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
533 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
534 boost::filesystem::create_directories (dir);
535 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
536 for (int i = 0; i < frames; ++i) {
537 writer->write (frame.data(), frame.size());
541 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
545 boost::filesystem::path
546 find_file (boost::filesystem::path dir, string filename_part)
548 boost::optional<boost::filesystem::path> found;
549 for (auto i: boost::filesystem::directory_iterator(dir)) {
550 if (i.path().filename().string().find(filename_part) != string::npos) {
551 BOOST_REQUIRE (!found);
555 BOOST_REQUIRE (found);
560 Editor::Editor(boost::filesystem::path path)
563 _content = dcp::file_to_string (_path);
569 auto f = fopen(_path.string().c_str(), "w");
571 fwrite (_content.c_str(), _content.length(), 1, f);
577 Editor::replace(string a, string b)
579 ChangeChecker cc(this);
580 boost::algorithm::replace_all (_content, a, b);
585 Editor::delete_first_line_containing(string s)
587 ChangeChecker cc(this);
588 auto lines = as_lines();
591 for (auto i: lines) {
592 if (i.find(s) == string::npos || done) {
593 _content += i + "\n";
601 Editor::delete_lines(string from, string to)
603 ChangeChecker cc(this);
604 auto lines = as_lines();
605 bool deleting = false;
607 for (auto i: lines) {
608 if (i.find(from) != string::npos) {
612 _content += i + "\n";
614 if (deleting && i.find(to) != string::npos) {
622 Editor::insert(string after, string line)
624 ChangeChecker cc(this);
625 auto lines = as_lines();
627 bool replaced = false;
628 for (auto i: lines) {
629 _content += i + "\n";
630 if (!replaced && i.find(after) != string::npos) {
631 _content += line + "\n";
639 Editor::delete_lines_after(string after, int lines_to_delete)
641 ChangeChecker cc(this);
642 auto lines = as_lines();
644 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
645 return line.find(after) != string::npos;
648 for (auto i = lines.begin(); i != lines.end(); ++i) {
650 to_delete = lines_to_delete;
651 _content += *i + "\n";
652 } else if (to_delete == 0) {
653 _content += *i + "\n";
662 Editor::as_lines() const
664 vector<string> lines;
665 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
670 BOOST_GLOBAL_FIXTURE (TestConfig);