Use std::vector for LUTs.
[libdcp.git] / test / test.cc
1 /*
2     Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 #define BOOST_TEST_DYN_LINK
35 #define BOOST_TEST_MODULE libdcp_test
36 #include "compose.hpp"
37 #include "cpl.h"
38 #include "dcp.h"
39 #include "interop_subtitle_asset.h"
40 #include "file.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"
47 #include "reel.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"
60 #include "test.h"
61 #include "util.h"
62 #include "warnings.h"
63 LIBDCP_DISABLE_WARNINGS
64 #include <asdcp/KM_util.h>
65 #include <asdcp/KM_prng.h>
66 LIBDCP_ENABLE_WARNINGS
67 #include <sndfile.h>
68 LIBDCP_DISABLE_WARNINGS
69 #include <libxml++/libxml++.h>
70 LIBDCP_ENABLE_WARNINGS
71 #include <boost/test/unit_test.hpp>
72 #include <cstdio>
73 #include <iostream>
74
75
76 using std::string;
77 using std::min;
78 using std::vector;
79 using std::shared_ptr;
80 using std::make_shared;
81 using boost::optional;
82
83
84 boost::filesystem::path private_test;
85 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
86
87
88 struct TestConfig
89 {
90         TestConfig()
91         {
92                 dcp::init ();
93                 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
94                         private_test = boost::unit_test::framework::master_test_suite().argv[1];
95                 }
96
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());
103                 }
104         }
105 };
106
107
108 void
109 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
110 {
111         BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
112         BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
113
114         if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
115                 return;
116         }
117
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;
121         };
122
123         auto ref_children = ref->get_children ();
124         auto test_children = test->get_children ();
125
126         auto k = ref_children.begin ();
127         auto l = test_children.begin ();
128         while (k != ref_children.end() && l != test_children.end()) {
129
130                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
131                         ++k;
132                         continue;
133                 }
134
135                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
136                         ++l;
137                         continue;
138                 }
139
140                 if (whitespace_content(*k) && ignore_whitespace) {
141                         ++k;
142                         continue;
143                 }
144
145                 if (whitespace_content(*l) && ignore_whitespace) {
146                         ++l;
147                         continue;
148                 }
149
150                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
151
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);
157                 }
158
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());
164                 }
165
166                 ++k;
167                 ++l;
168         }
169
170         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
171                 ++k;
172         }
173
174         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
175                 ++l;
176         }
177
178         BOOST_REQUIRE (k == ref_children.end());
179         BOOST_REQUIRE (l == test_children.end());
180
181         auto ref_attributes = ref->get_attributes ();
182         auto test_attributes = test->get_attributes ();
183         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
184
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());
190
191                 ++m;
192                 ++n;
193         }
194 }
195
196 void
197 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
198 {
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 ();
205
206         check_xml (ref_root, test_root, ignore, ignore_whitespace);
207 }
208
209 void
210 check_file (boost::filesystem::path ref, boost::filesystem::path check)
211 {
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);
218
219         int const buffer_size = 65536;
220         std::vector<uint8_t> ref_buffer(buffer_size);
221         std::vector<uint8_t> check_buffer(buffer_size);
222
223         uintmax_t pos = 0;
224
225         while (pos < 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);
231
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 (
236                                                 false,
237                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
238                                                 );
239                                         break;
240                                 }
241                         }
242                         break;
243                 }
244
245                 pos += this_time;
246         }
247 }
248
249
250 RNGFixer::RNGFixer ()
251 {
252         Kumu::cth_test = true;
253         Kumu::FortunaRNG().Reset();
254 }
255
256
257 RNGFixer::~RNGFixer ()
258 {
259         Kumu::cth_test = false;
260 }
261
262
263 shared_ptr<dcp::MonoPictureAsset>
264 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
265 {
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";
270
271         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
272         mp->set_metadata (mxf_meta);
273         if (key) {
274                 mp->set_key (*key);
275         }
276         auto picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
277
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);
282         }
283         auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
284
285         for (int i = 0; i < frames; ++i) {
286                 picture_writer->write (j2c.data(), j2c.size());
287         }
288         picture_writer->finalize ();
289
290         return mp;
291 }
292
293
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)
296 {
297         int const channels = 6;
298
299         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
300         auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
301         if (key) {
302                 ms->set_key (*key);
303         }
304         ms->_language = language;
305         ms->set_metadata (mxf_meta);
306         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
307
308         int const samples_per_frame = sample_rate / 24;
309
310         float* silence[channels];
311         for (auto i = 0; i < channels; ++i) {
312                 silence[i] = new float[samples_per_frame];
313                 memset (silence[i], 0, samples_per_frame * sizeof(float));
314         }
315
316         for (auto i = 0; i < frames; ++i) {
317                 sound_writer->write (silence, samples_per_frame);
318         }
319
320         sound_writer->finalize ();
321
322         for (auto i = 0; i < channels; ++i) {
323                 delete[] silence[i];
324         }
325
326         return ms;
327 }
328
329
330 shared_ptr<dcp::DCP>
331 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
332 {
333         /* Some known metadata */
334         dcp::MXFMetadata mxf_meta;
335         mxf_meta.company_name = "OpenDCP";
336         mxf_meta.product_name = "OpenDCP";
337         mxf_meta.product_version = "0.0.25";
338
339         auto constexpr sample_rate = 48000;
340
341         boost::filesystem::remove_all (path);
342         boost::filesystem::create_directories (path);
343         auto d = make_shared<dcp::DCP>(path);
344         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
345         cpl->set_annotation_text ("A Test DCP");
346         cpl->set_issuer ("OpenDCP 0.0.25");
347         cpl->set_creator ("OpenDCP 0.0.25");
348         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
349         cpl->set_content_version (
350                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
351                 );
352         cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
353         cpl->set_main_sound_sample_rate(sample_rate);
354         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
355         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
356         cpl->set_version_number(1);
357
358         for (int i = 0; i < reels; ++i) {
359                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
360
361                 auto mp = simple_picture (path, suffix, frames, key);
362                 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
363
364                 auto reel = make_shared<dcp::Reel>(
365                         shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
366                         shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
367                         );
368
369                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
370                 if (i == 0) {
371                         markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
372                 }
373                 if (i == reels - 1) {
374                         markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
375                 }
376                 reel->add (markers);
377
378                 cpl->add (reel);
379         }
380
381         d->add (cpl);
382         return d;
383 }
384
385
386 shared_ptr<dcp::Subtitle>
387 simple_subtitle ()
388 {
389         return make_shared<dcp::SubtitleString>(
390                 optional<string>(),
391                 false,
392                 false,
393                 false,
394                 dcp::Colour(255, 255, 255),
395                 42,
396                 1,
397                 dcp::Time(0, 0, 4, 0, 24),
398                 dcp::Time(0, 0, 8, 0, 24),
399                 0.5,
400                 dcp::HAlign::CENTER,
401                 0.8,
402                 dcp::VAlign::TOP,
403                 dcp::Direction::LTR,
404                 "Hello world",
405                 dcp::Effect::NONE,
406                 dcp::Colour(255, 255, 255),
407                 dcp::Time(),
408                 dcp::Time(),
409                 0
410                 );
411 }
412
413
414 shared_ptr<dcp::ReelMarkersAsset>
415 simple_markers (int frames)
416 {
417         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
418         markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
419         markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
420         return markers;
421 }
422
423
424 shared_ptr<dcp::DCP>
425 make_simple_with_interop_subs (boost::filesystem::path path)
426 {
427         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
428
429         auto subs = make_shared<dcp::InteropSubtitleAsset>();
430         subs->add (simple_subtitle());
431
432         boost::filesystem::create_directory (path / "subs");
433         dcp::ArrayData data(4096);
434         subs->add_font ("afont", data);
435         subs->write (path / "subs" / "subs.xml");
436
437         auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
438         dcp->cpls().front()->reels().front()->add (reel_subs);
439
440         return dcp;
441 }
442
443
444 shared_ptr<dcp::DCP>
445 make_simple_with_smpte_subs (boost::filesystem::path path)
446 {
447         auto dcp = make_simple (path, 1, 192);
448
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());
453
454         subs->write (path / "subs.mxf");
455
456         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
457         dcp->cpls().front()->reels().front()->add (reel_subs);
458
459         return dcp;
460 }
461
462
463 shared_ptr<dcp::DCP>
464 make_simple_with_interop_ccaps (boost::filesystem::path path)
465 {
466         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
467
468         auto subs = make_shared<dcp::InteropSubtitleAsset>();
469         subs->add (simple_subtitle());
470         subs->write (path / "ccap.xml");
471
472         auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
473         dcp->cpls()[0]->reels()[0]->add (reel_caps);
474
475         return dcp;
476 }
477
478
479 shared_ptr<dcp::DCP>
480 make_simple_with_smpte_ccaps (boost::filesystem::path path)
481 {
482         auto dcp = make_simple (path, 1, 192);
483
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");
489
490         auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
491         dcp->cpls()[0]->reels()[0]->add(reel_caps);
492
493         return dcp;
494 }
495
496
497 shared_ptr<dcp::OpenJPEGImage>
498 black_image (dcp::Size size)
499 {
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));
504         }
505         return image;
506 }
507
508
509 shared_ptr<dcp::ReelAsset>
510 black_picture_asset (boost::filesystem::path dir, int frames)
511 {
512         auto image = black_image ();
513         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
514         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
515
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", true);
520         for (int i = 0; i < frames; ++i) {
521                 writer->write (frame.data(), frame.size());
522         }
523         writer->finalize ();
524
525         return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
526 }
527
528
529 boost::filesystem::path
530 find_file (boost::filesystem::path dir, string filename_part)
531 {
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);
536                         found = i;
537                 }
538         }
539         BOOST_REQUIRE (found);
540         return *found;
541 }
542
543
544 BOOST_GLOBAL_FIXTURE (TestConfig);