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 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
95 private_test = boost::unit_test::framework::master_test_suite().argv[1];
98 using namespace boost::filesystem;
99 boost::system::error_code ec;
100 remove_all (xsd_test, ec);
101 boost::filesystem::create_directory (xsd_test);
102 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
103 copy_file (*i, xsd_test / i->path().filename());
110 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
112 BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
113 BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
115 if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
119 auto whitespace_content = [](xmlpp::Node* node) {
120 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
121 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
124 auto ref_children = ref->get_children ();
125 auto test_children = test->get_children ();
127 auto k = ref_children.begin ();
128 auto l = test_children.begin ();
129 while (k != ref_children.end() && l != test_children.end()) {
131 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
136 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
141 if (whitespace_content(*k) && ignore_whitespace) {
146 if (whitespace_content(*l) && ignore_whitespace) {
151 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
153 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
154 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
155 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
156 if (ref_el && test_el) {
157 check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
160 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
161 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
162 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
163 if (ref_cn && test_cn) {
164 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
171 while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
175 while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
179 BOOST_REQUIRE (k == ref_children.end());
180 BOOST_REQUIRE (l == test_children.end());
182 auto ref_attributes = ref->get_attributes ();
183 auto test_attributes = test->get_attributes ();
184 BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
186 auto m = ref_attributes.begin();
187 auto n = test_attributes.begin();
188 while (m != ref_attributes.end ()) {
189 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
190 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
198 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
200 auto ref_parser = new xmlpp::DomParser ();
201 ref_parser->parse_memory (ref);
202 auto ref_root = ref_parser->get_document()->get_root_node ();
203 auto test_parser = new xmlpp::DomParser ();
204 test_parser->parse_memory (test);
205 auto test_root = test_parser->get_document()->get_root_node ();
207 check_xml (ref_root, test_root, ignore, ignore_whitespace);
211 check_file (boost::filesystem::path ref, boost::filesystem::path check)
213 uintmax_t size = boost::filesystem::file_size (ref);
214 BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
215 dcp::File ref_file(ref, "rb");
216 BOOST_REQUIRE (ref_file);
217 dcp::File check_file(check, "rb");
218 BOOST_REQUIRE (check_file);
220 int const buffer_size = 65536;
221 std::vector<uint8_t> ref_buffer(buffer_size);
222 std::vector<uint8_t> check_buffer(buffer_size);
227 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
228 size_t r = ref_file.read(ref_buffer.data(), 1, this_time);
229 BOOST_CHECK_EQUAL (r, this_time);
230 r = check_file.read(check_buffer.data(), 1, this_time);
231 BOOST_CHECK_EQUAL (r, this_time);
233 if (memcmp(ref_buffer.data(), check_buffer.data(), this_time) != 0) {
234 for (int i = 0; i < buffer_size; ++i) {
235 if (ref_buffer[i] != check_buffer[i]) {
236 BOOST_CHECK_MESSAGE (
238 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
251 RNGFixer::RNGFixer ()
253 Kumu::cth_test = true;
254 Kumu::FortunaRNG().Reset();
258 RNGFixer::~RNGFixer ()
260 Kumu::cth_test = false;
264 shared_ptr<dcp::MonoPictureAsset>
265 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
267 dcp::MXFMetadata mxf_meta;
268 mxf_meta.company_name = "OpenDCP";
269 mxf_meta.product_name = "OpenDCP";
270 mxf_meta.product_version = "0.0.25";
272 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
273 mp->set_metadata (mxf_meta);
277 auto picture_writer = mp->start_write(path / dcp::String::compose("video%1.mxf", suffix), dcp::PictureAsset::Behaviour::MAKE_NEW);
279 dcp::Size const size (1998, 1080);
280 auto image = make_shared<dcp::OpenJPEGImage>(size);
281 for (int i = 0; i < 3; ++i) {
282 memset (image->data(i), 0, 2 * size.width * size.height);
284 auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
286 for (int i = 0; i < frames; ++i) {
287 picture_writer->write (j2c.data(), j2c.size());
289 picture_writer->finalize ();
295 shared_ptr<dcp::SoundAsset>
296 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)
298 /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
299 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
303 ms->_language = language;
304 ms->set_metadata (mxf_meta);
305 auto sound_writer = ms->start_write(path / dcp::String::compose("audio%1.mxf", suffix), {}, dcp::SoundAsset::AtmosSync::DISABLED, dcp::SoundAsset::MCASubDescriptors::ENABLED);
307 int const samples_per_frame = sample_rate / 24;
309 float* silence[channels];
310 for (auto i = 0; i < channels; ++i) {
311 silence[i] = new float[samples_per_frame];
312 memset (silence[i], 0, samples_per_frame * sizeof(float));
315 for (auto i = 0; i < frames; ++i) {
316 sound_writer->write(silence, channels, samples_per_frame);
319 sound_writer->finalize ();
321 for (auto i = 0; i < channels; ++i) {
330 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
332 /* Some known metadata */
333 dcp::MXFMetadata mxf_meta;
334 mxf_meta.company_name = "OpenDCP";
335 mxf_meta.product_name = "OpenDCP";
336 mxf_meta.product_version = "0.0.25";
338 auto constexpr sample_rate = 48000;
340 boost::filesystem::remove_all (path);
341 boost::filesystem::create_directories (path);
342 auto d = make_shared<dcp::DCP>(path);
343 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
344 cpl->set_annotation_text ("A Test DCP");
345 cpl->set_issuer ("OpenDCP 0.0.25");
346 cpl->set_creator ("OpenDCP 0.0.25");
347 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
348 cpl->set_content_version (
349 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
351 cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
352 cpl->set_main_sound_sample_rate(sample_rate);
353 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
354 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
355 cpl->set_version_number(1);
357 for (int i = 0; i < reels; ++i) {
358 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
360 auto mp = simple_picture (path, suffix, frames, key);
361 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
363 auto reel = make_shared<dcp::Reel>(
364 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
365 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
368 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
370 markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
372 if (i == reels - 1) {
373 markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
380 d->set_annotation_text("A Test DCP");
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());
455 dcp::ArrayData fake_font(1024);
456 subs->add_font("font", fake_font);
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 dcp::ArrayData fake_font(1024);
493 subs->add_font("font", fake_font);
494 subs->write (path / "ccap.mxf");
496 auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
497 dcp->cpls()[0]->reels()[0]->add(reel_caps);
503 shared_ptr<dcp::OpenJPEGImage>
504 black_image (dcp::Size size)
506 auto image = make_shared<dcp::OpenJPEGImage>(size);
507 int const pixels = size.width * size.height;
508 for (int i = 0; i < 3; ++i) {
509 memset (image->data(i), 0, pixels * sizeof(int));
515 shared_ptr<dcp::ReelAsset>
516 black_picture_asset (boost::filesystem::path dir, int frames)
518 auto image = black_image ();
519 auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
520 BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
522 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
523 asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
524 boost::filesystem::create_directories (dir);
525 auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
526 for (int i = 0; i < frames; ++i) {
527 writer->write (frame.data(), frame.size());
531 return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
535 boost::filesystem::path
536 find_file (boost::filesystem::path dir, string filename_part)
538 boost::optional<boost::filesystem::path> found;
539 for (auto i: boost::filesystem::directory_iterator(dir)) {
540 if (i.path().filename().string().find(filename_part) != string::npos) {
541 BOOST_REQUIRE (!found);
545 BOOST_REQUIRE (found);
550 Editor::Editor(boost::filesystem::path path)
553 _content = dcp::file_to_string (_path);
559 auto f = fopen(_path.string().c_str(), "w");
561 fwrite (_content.c_str(), _content.length(), 1, f);
567 Editor::replace(string a, string b)
569 ChangeChecker cc(this);
570 boost::algorithm::replace_all (_content, a, b);
575 Editor::delete_first_line_containing(string s)
577 ChangeChecker cc(this);
578 auto lines = as_lines();
581 for (auto i: lines) {
582 if (i.find(s) == string::npos || done) {
583 _content += i + "\n";
591 Editor::delete_lines(string from, string to)
593 ChangeChecker cc(this);
594 auto lines = as_lines();
595 bool deleting = false;
597 for (auto i: lines) {
598 if (i.find(from) != string::npos) {
602 _content += i + "\n";
604 if (deleting && i.find(to) != string::npos) {
612 Editor::insert(string after, string line)
614 ChangeChecker cc(this);
615 auto lines = as_lines();
617 bool replaced = false;
618 for (auto i: lines) {
619 _content += i + "\n";
620 if (!replaced && i.find(after) != string::npos) {
621 _content += line + "\n";
629 Editor::delete_lines_after(string after, int lines_to_delete)
631 ChangeChecker cc(this);
632 auto lines = as_lines();
634 auto iter = std::find_if(lines.begin(), lines.end(), [after](string const& line) {
635 return line.find(after) != string::npos;
638 for (auto i = lines.begin(); i != lines.end(); ++i) {
640 to_delete = lines_to_delete;
641 _content += *i + "\n";
642 } else if (to_delete == 0) {
643 _content += *i + "\n";
652 Editor::as_lines() const
654 vector<string> lines;
655 boost::algorithm::split(lines, _content, boost::is_any_of("\r\n"), boost::token_compress_on);
660 BOOST_GLOBAL_FIXTURE (TestConfig);