Don't use flat_red.j2c for the simple DCP test as it's only 8-bit.
[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 "mono_picture_asset.h"
41 #include "picture_asset_writer.h"
42 #include "reel.h"
43 #include "reel_mono_picture_asset.h"
44 #include "reel_sound_asset.h"
45 #include "reel_closed_caption_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "sound_asset.h"
48 #include "sound_asset_writer.h"
49 #include "smpte_subtitle_asset.h"
50 #include "mono_picture_asset.h"
51 #include "openjpeg_image.h"
52 #include "j2k_transcode.h"
53 #include "picture_asset_writer.h"
54 #include "reel_mono_picture_asset.h"
55 #include "reel_asset.h"
56 #include "test.h"
57 #include "util.h"
58 #include "reel_markers_asset.h"
59 #include <asdcp/KM_util.h>
60 #include <asdcp/KM_prng.h>
61 #include <sndfile.h>
62 #include <libxml++/libxml++.h>
63 #include <boost/test/unit_test.hpp>
64 #include <cstdio>
65 #include <iostream>
66
67 using std::string;
68 using std::min;
69 using std::vector;
70 using std::shared_ptr;
71 using std::make_shared;
72 using boost::optional;
73
74
75 boost::filesystem::path private_test;
76 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
77
78
79 struct TestConfig
80 {
81         TestConfig()
82         {
83                 dcp::init ();
84                 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
85                         private_test = boost::unit_test::framework::master_test_suite().argv[1];
86                 }
87
88                 using namespace boost::filesystem;
89                 boost::system::error_code ec;
90                 remove_all (xsd_test, ec);
91                 boost::filesystem::create_directory (xsd_test);
92                 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
93                         copy_file (*i, xsd_test / i->path().filename());
94                 }
95         }
96 };
97
98 void
99 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
100 {
101         BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
102         BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
103
104         if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
105                 return;
106         }
107
108         auto whitespace_content = [](xmlpp::Node* node) {
109                 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
110                 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
111         };
112
113         auto ref_children = ref->get_children ();
114         auto test_children = test->get_children ();
115
116         auto k = ref_children.begin ();
117         auto l = test_children.begin ();
118         while (k != ref_children.end() && l != test_children.end()) {
119
120                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
121                         ++k;
122                         continue;
123                 }
124
125                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
126                         ++l;
127                         continue;
128                 }
129
130                 if (whitespace_content(*k) && ignore_whitespace) {
131                         ++k;
132                         continue;
133                 }
134
135                 if (whitespace_content(*l) && ignore_whitespace) {
136                         ++l;
137                         continue;
138                 }
139
140                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
141
142                 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
143                 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
144                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
145                 if (ref_el && test_el) {
146                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
147                 }
148
149                 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
150                 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
151                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
152                 if (ref_cn && test_cn) {
153                         BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
154                 }
155
156                 ++k;
157                 ++l;
158         }
159
160         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
161                 ++k;
162         }
163
164         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
165                 ++l;
166         }
167
168         BOOST_REQUIRE (k == ref_children.end());
169         BOOST_REQUIRE (l == test_children.end());
170
171         auto ref_attributes = ref->get_attributes ();
172         auto test_attributes = test->get_attributes ();
173         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
174
175         auto m = ref_attributes.begin();
176         auto n = test_attributes.begin();
177         while (m != ref_attributes.end ()) {
178                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
179                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
180
181                 ++m;
182                 ++n;
183         }
184 }
185
186 void
187 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
188 {
189         xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
190         ref_parser->parse_memory (ref);
191         xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
192         xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
193         test_parser->parse_memory (test);
194         xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
195
196         check_xml (ref_root, test_root, ignore, ignore_whitespace);
197 }
198
199 void
200 check_file (boost::filesystem::path ref, boost::filesystem::path check)
201 {
202         uintmax_t size = boost::filesystem::file_size (ref);
203         BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
204         FILE* ref_file = dcp::fopen_boost (ref, "rb");
205         BOOST_REQUIRE (ref_file);
206         FILE* check_file = dcp::fopen_boost (check, "rb");
207         BOOST_REQUIRE (check_file);
208
209         int const buffer_size = 65536;
210         uint8_t* ref_buffer = new uint8_t[buffer_size];
211         uint8_t* check_buffer = new uint8_t[buffer_size];
212
213         uintmax_t pos = 0;
214
215         while (pos < size) {
216                 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
217                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
218                 BOOST_CHECK_EQUAL (r, this_time);
219                 r = fread (check_buffer, 1, this_time, check_file);
220                 BOOST_CHECK_EQUAL (r, this_time);
221
222                 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
223                         for (int i = 0; i < buffer_size; ++i) {
224                                 if (ref_buffer[i] != check_buffer[i]) {
225                                         BOOST_CHECK_MESSAGE (
226                                                 false,
227                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
228                                                 );
229                                         break;
230                                 }
231                         }
232                         break;
233                 }
234
235                 pos += this_time;
236         }
237
238         delete[] ref_buffer;
239         delete[] check_buffer;
240
241         fclose (ref_file);
242         fclose (check_file);
243 }
244
245
246 RNGFixer::RNGFixer ()
247 {
248         Kumu::cth_test = true;
249         Kumu::FortunaRNG().Reset();
250 }
251
252
253 RNGFixer::~RNGFixer ()
254 {
255         Kumu::cth_test = false;
256 }
257
258
259 shared_ptr<dcp::MonoPictureAsset>
260 simple_picture (boost::filesystem::path path, string suffix, int frames)
261 {
262         dcp::MXFMetadata mxf_meta;
263         mxf_meta.company_name = "OpenDCP";
264         mxf_meta.product_name = "OpenDCP";
265         mxf_meta.product_version = "0.0.25";
266
267         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
268         mp->set_metadata (mxf_meta);
269         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
270
271         dcp::Size const size (1998, 1080);
272         auto image = make_shared<dcp::OpenJPEGImage>(size);
273         for (int i = 0; i < 3; ++i) {
274                 memset (image->data(i), 0, 2 * size.width * size.height);
275         }
276         auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
277
278         for (int i = 0; i < frames; ++i) {
279                 picture_writer->write (j2c.data(), j2c.size());
280         }
281         picture_writer->finalize ();
282
283         return mp;
284 }
285
286
287 shared_ptr<dcp::SoundAsset>
288 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
289 {
290         int const channels = 6;
291
292         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
293         shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
294         ms->_language = language;
295         ms->set_metadata (mxf_meta);
296         vector<dcp::Channel> active_channels;
297         active_channels.push_back (dcp::Channel::LEFT);
298         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
299
300         int const samples_per_frame = sample_rate / 24;
301
302         float* silence[channels];
303         for (auto i = 0; i < channels; ++i) {
304                 silence[i] = new float[samples_per_frame];
305                 memset (silence[i], 0, samples_per_frame * sizeof(float));
306         }
307
308         for (auto i = 0; i < frames; ++i) {
309                 sound_writer->write (silence, samples_per_frame);
310         }
311
312         sound_writer->finalize ();
313
314         for (auto i = 0; i < channels; ++i) {
315                 delete[] silence[i];
316         }
317
318         return ms;
319 }
320
321
322 shared_ptr<dcp::DCP>
323 make_simple (boost::filesystem::path path, int reels, int frames)
324 {
325         /* Some known metadata */
326         dcp::MXFMetadata mxf_meta;
327         mxf_meta.company_name = "OpenDCP";
328         mxf_meta.product_name = "OpenDCP";
329         mxf_meta.product_version = "0.0.25";
330
331         boost::filesystem::remove_all (path);
332         boost::filesystem::create_directories (path);
333         auto d = make_shared<dcp::DCP>(path);
334         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER);
335         cpl->set_annotation_text ("A Test DCP");
336         cpl->set_issuer ("OpenDCP 0.0.25");
337         cpl->set_creator ("OpenDCP 0.0.25");
338         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
339         cpl->set_content_version (
340                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
341                 );
342         cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
343         cpl->set_main_sound_sample_rate(48000);
344         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
345         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
346         cpl->set_version_number(1);
347
348         for (int i = 0; i < reels; ++i) {
349                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
350
351                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
352                 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
353
354                 auto reel = make_shared<dcp::Reel>(
355                         shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
356                         shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
357                         );
358
359                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
360                 if (i == 0) {
361                         markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
362                 }
363                 if (i == reels - 1) {
364                         markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
365                 }
366                 reel->add (markers);
367
368                 cpl->add (reel);
369         }
370
371         d->add (cpl);
372         return d;
373 }
374
375
376 shared_ptr<dcp::Subtitle>
377 simple_subtitle ()
378 {
379         return make_shared<dcp::SubtitleString>(
380                 optional<string>(),
381                 false,
382                 false,
383                 false,
384                 dcp::Colour(255, 255, 255),
385                 42,
386                 1,
387                 dcp::Time(0, 0, 4, 0, 24),
388                 dcp::Time(0, 0, 8, 0, 24),
389                 0.5,
390                 dcp::HAlign::CENTER,
391                 0.8,
392                 dcp::VAlign::TOP,
393                 dcp::Direction::LTR,
394                 "Hello world",
395                 dcp::Effect::NONE,
396                 dcp::Colour(255, 255, 255),
397                 dcp::Time(),
398                 dcp::Time()
399                 );
400 }
401
402
403 shared_ptr<dcp::ReelMarkersAsset>
404 simple_markers (int frames)
405 {
406         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
407         markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
408         markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
409         return markers;
410 }
411
412
413 shared_ptr<dcp::DCP>
414 make_simple_with_interop_subs (boost::filesystem::path path)
415 {
416         shared_ptr<dcp::DCP> dcp = make_simple (path);
417
418         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
419         subs->add (simple_subtitle());
420
421         boost::filesystem::create_directory (path / "subs");
422         dcp::ArrayData data(4096);
423         subs->add_font ("afont", data);
424         subs->write (path / "subs" / "subs.xml");
425
426         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
427         dcp->cpls().front()->reels().front()->add (reel_subs);
428
429         return dcp;
430 }
431
432
433 shared_ptr<dcp::DCP>
434 make_simple_with_smpte_subs (boost::filesystem::path path)
435 {
436         shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
437
438         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
439         subs->set_language (dcp::LanguageTag("de-DE"));
440         subs->set_start_time (dcp::Time());
441         subs->add (simple_subtitle());
442
443         subs->write (path / "subs.mxf");
444
445         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
446         dcp->cpls().front()->reels().front()->add (reel_subs);
447
448         return dcp;
449 }
450
451
452 shared_ptr<dcp::DCP>
453 make_simple_with_interop_ccaps (boost::filesystem::path path)
454 {
455         shared_ptr<dcp::DCP> dcp = make_simple (path);
456
457         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
458         subs->add (simple_subtitle());
459         subs->write (path / "ccap.xml");
460
461         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
462         dcp->cpls().front()->reels().front()->add (reel_caps);
463
464         return dcp;
465 }
466
467
468 shared_ptr<dcp::DCP>
469 make_simple_with_smpte_ccaps (boost::filesystem::path path)
470 {
471         shared_ptr<dcp::DCP> dcp = make_simple (path, 1, 240);
472
473         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
474         subs->set_language (dcp::LanguageTag("de-DE"));
475         subs->set_start_time (dcp::Time());
476         subs->add (simple_subtitle());
477         subs->write (path / "ccap.mxf");
478
479         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
480         dcp->cpls().front()->reels().front()->add (reel_caps);
481
482         return dcp;
483 }
484
485
486 shared_ptr<dcp::OpenJPEGImage>
487 black_image (dcp::Size size)
488 {
489         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
490         int const pixels = size.width * size.height;
491         for (int i = 0; i < 3; ++i) {
492                 memset (image->data(i), 0, pixels * sizeof(int));
493         }
494         return image;
495 }
496
497
498 shared_ptr<dcp::ReelAsset>
499 black_picture_asset (boost::filesystem::path dir, int frames)
500 {
501         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
502         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
503         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
504
505         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
506         asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
507         boost::filesystem::create_directories (dir);
508         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
509         for (int i = 0; i < frames; ++i) {
510                 writer->write (frame.data(), frame.size());
511         }
512         writer->finalize ();
513
514         return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
515 }
516
517
518 BOOST_GLOBAL_FIXTURE (TestConfig);