Rename Data -> ArrayData.
[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 "file.h"
40 #include "interop_subtitle_asset.h"
41 #include "mono_picture_asset.h"
42 #include "picture_asset_writer.h"
43 #include "reel.h"
44 #include "reel_mono_picture_asset.h"
45 #include "reel_sound_asset.h"
46 #include "reel_closed_caption_asset.h"
47 #include "reel_subtitle_asset.h"
48 #include "sound_asset.h"
49 #include "sound_asset_writer.h"
50 #include "smpte_subtitle_asset.h"
51 #include "mono_picture_asset.h"
52 #include "openjpeg_image.h"
53 #include "j2k.h"
54 #include "picture_asset_writer.h"
55 #include "reel_mono_picture_asset.h"
56 #include "reel_asset.h"
57 #include "test.h"
58 #include "util.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::list;
70 using std::vector;
71 using boost::shared_ptr;
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, list<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         xmlpp::Element::NodeList ref_children = ref->get_children ();
109         xmlpp::Element::NodeList test_children = test->get_children ();
110         BOOST_REQUIRE_MESSAGE (
111                 ref_children.size () == test_children.size (),
112                 "child counts of " << ref->get_name() << " differ; ref has " << ref_children.size() << ", test has " << test_children.size()
113                 );
114
115         xmlpp::Element::NodeList::iterator k = ref_children.begin ();
116         xmlpp::Element::NodeList::iterator l = test_children.begin ();
117         while (k != ref_children.end ()) {
118
119                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
120
121                 xmlpp::Element* ref_el = dynamic_cast<xmlpp::Element*> (*k);
122                 xmlpp::Element* test_el = dynamic_cast<xmlpp::Element*> (*l);
123                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
124                 if (ref_el && test_el) {
125                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
126                 }
127
128                 xmlpp::ContentNode* ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
129                 xmlpp::ContentNode* test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
130                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
131                 if (ref_cn && test_cn) {
132                         if (
133                                 !ignore_whitespace ||
134                                 ref_cn->get_content().find_first_not_of(" \t\r\n") != string::npos ||
135                                 test_cn->get_content().find_first_not_of(" \t\r\n") != string::npos) {
136
137                                 BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content ());
138                         }
139                 }
140
141                 ++k;
142                 ++l;
143         }
144
145         xmlpp::Element::AttributeList ref_attributes = ref->get_attributes ();
146         xmlpp::Element::AttributeList test_attributes = test->get_attributes ();
147         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
148
149         xmlpp::Element::AttributeList::const_iterator m = ref_attributes.begin();
150         xmlpp::Element::AttributeList::const_iterator n = test_attributes.begin();
151         while (m != ref_attributes.end ()) {
152                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
153                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
154
155                 ++m;
156                 ++n;
157         }
158 }
159
160 void
161 check_xml (string ref, string test, list<string> ignore)
162 {
163         xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
164         ref_parser->parse_memory (ref);
165         xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
166         xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
167         test_parser->parse_memory (test);
168         xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
169
170         check_xml (ref_root, test_root, ignore);
171 }
172
173 void
174 check_file (boost::filesystem::path ref, boost::filesystem::path check)
175 {
176         uintmax_t N = boost::filesystem::file_size (ref);
177         BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
178         FILE* ref_file = dcp::fopen_boost (ref, "rb");
179         BOOST_REQUIRE (ref_file);
180         FILE* check_file = dcp::fopen_boost (check, "rb");
181         BOOST_REQUIRE (check_file);
182
183         int const buffer_size = 65536;
184         uint8_t* ref_buffer = new uint8_t[buffer_size];
185         uint8_t* check_buffer = new uint8_t[buffer_size];
186
187         string error;
188         error = "File " + check.string() + " differs from reference " + ref.string();
189
190         while (N) {
191                 uintmax_t this_time = min (uintmax_t (buffer_size), N);
192                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
193                 BOOST_CHECK_EQUAL (r, this_time);
194                 r = fread (check_buffer, 1, this_time, check_file);
195                 BOOST_CHECK_EQUAL (r, this_time);
196
197                 BOOST_CHECK_MESSAGE (memcmp (ref_buffer, check_buffer, this_time) == 0, error);
198                 if (memcmp (ref_buffer, check_buffer, this_time)) {
199                         break;
200                 }
201
202                 N -= this_time;
203         }
204
205         delete[] ref_buffer;
206         delete[] check_buffer;
207
208         fclose (ref_file);
209         fclose (check_file);
210 }
211
212
213 RNGFixer::RNGFixer ()
214 {
215         Kumu::cth_test = true;
216         Kumu::FortunaRNG().Reset();
217 }
218
219
220 RNGFixer::~RNGFixer ()
221 {
222         Kumu::cth_test = false;
223 }
224
225
226 shared_ptr<dcp::MonoPictureAsset>
227 simple_picture (boost::filesystem::path path, string suffix)
228 {
229         dcp::MXFMetadata mxf_meta;
230         mxf_meta.company_name = "OpenDCP";
231         mxf_meta.product_name = "OpenDCP";
232         mxf_meta.product_version = "0.0.25";
233
234         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
235         mp->set_metadata (mxf_meta);
236         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
237         dcp::File j2c ("test/data/32x32_red_square.j2c");
238         for (int i = 0; i < 24; ++i) {
239                 picture_writer->write (j2c.data (), j2c.size ());
240         }
241         picture_writer->finalize ();
242
243         return mp;
244 }
245
246
247 shared_ptr<dcp::DCP>
248 make_simple (boost::filesystem::path path, int reels)
249 {
250         /* Some known metadata */
251         dcp::MXFMetadata mxf_meta;
252         mxf_meta.company_name = "OpenDCP";
253         mxf_meta.product_name = "OpenDCP";
254         mxf_meta.product_version = "0.0.25";
255
256         boost::filesystem::remove_all (path);
257         boost::filesystem::create_directories (path);
258         shared_ptr<dcp::DCP> d (new dcp::DCP (path));
259         shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
260         cpl->set_annotation_text ("A Test DCP");
261         cpl->set_issuer ("OpenDCP 0.0.25");
262         cpl->set_creator ("OpenDCP 0.0.25");
263         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
264         cpl->set_content_version (
265                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
266                 );
267
268         for (int i = 0; i < reels; ++i) {
269                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
270
271                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix);
272
273                 shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-US"), dcp::SMPTE));
274                 ms->set_metadata (mxf_meta);
275                 vector<dcp::Channel> active_channels;
276                 active_channels.push_back (dcp::LEFT);
277                 shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
278
279                 SF_INFO info;
280                 info.format = 0;
281                 SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
282                 BOOST_CHECK (sndfile);
283                 float buffer[4096*6];
284                 float* channels[1];
285                 channels[0] = buffer;
286                 while (true) {
287                         sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
288                         sound_writer->write (channels, N);
289                         if (N < 4096) {
290                                 break;
291                         }
292                 }
293
294                 sound_writer->finalize ();
295
296                 cpl->add (shared_ptr<dcp::Reel> (
297                                   new dcp::Reel (
298                                           shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
299                                           shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
300                                           )
301                                   ));
302         }
303
304         d->add (cpl);
305         return d;
306 }
307
308
309 shared_ptr<dcp::Subtitle>
310 simple_subtitle ()
311 {
312         return shared_ptr<dcp::Subtitle>(
313                 new dcp::SubtitleString(
314                         optional<string>(),
315                         false,
316                         false,
317                         false,
318                         dcp::Colour(255, 255, 255),
319                         42,
320                         1,
321                         dcp::Time(0, 0, 4, 0, 24),
322                         dcp::Time(0, 0, 8, 0, 24),
323                         0.5,
324                         dcp::HALIGN_CENTER,
325                         0.8,
326                         dcp::VALIGN_TOP,
327                         dcp::DIRECTION_LTR,
328                         "Hello world",
329                         dcp::NONE,
330                         dcp::Colour(255, 255, 255),
331                         dcp::Time(),
332                         dcp::Time()
333                         )
334                 );
335 }
336
337
338 shared_ptr<dcp::DCP>
339 make_simple_with_interop_subs (boost::filesystem::path path)
340 {
341         shared_ptr<dcp::DCP> dcp = make_simple (path);
342
343         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
344         subs->add (simple_subtitle());
345
346         boost::filesystem::create_directory (path / "subs");
347         dcp::ArrayData data(4096);
348         data.write (path / "subs" / "font.ttf");
349         subs->add_font ("afont", path / "subs" / "font.ttf");
350         subs->write (path / "subs" / "subs.xml");
351
352         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
353         dcp->cpls().front()->reels().front()->add (reel_subs);
354
355         return dcp;
356 }
357
358
359 shared_ptr<dcp::DCP>
360 make_simple_with_smpte_subs (boost::filesystem::path path)
361 {
362         shared_ptr<dcp::DCP> dcp = make_simple (path);
363
364         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
365         subs->add (simple_subtitle());
366
367         dcp::ArrayData data(4096);
368         subs->write (path / "subs.mxf");
369
370         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
371         dcp->cpls().front()->reels().front()->add (reel_subs);
372
373         return dcp;
374 }
375
376
377 shared_ptr<dcp::DCP>
378 make_simple_with_interop_ccaps (boost::filesystem::path path)
379 {
380         shared_ptr<dcp::DCP> dcp = make_simple (path);
381
382         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
383         subs->add (simple_subtitle());
384         subs->write (path / "ccap.xml");
385
386         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
387         dcp->cpls().front()->reels().front()->add (reel_caps);
388
389         return dcp;
390 }
391
392
393 shared_ptr<dcp::DCP>
394 make_simple_with_smpte_ccaps (boost::filesystem::path path)
395 {
396         shared_ptr<dcp::DCP> dcp = make_simple (path);
397
398         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
399         subs->add (simple_subtitle());
400         subs->write (path / "ccap.mxf");
401
402         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
403         dcp->cpls().front()->reels().front()->add (reel_caps);
404
405         return dcp;
406 }
407
408
409 shared_ptr<dcp::OpenJPEGImage>
410 black_image ()
411 {
412         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
413         int const pixels = 1998 * 1080;
414         for (int i = 0; i < 3; ++i) {
415                 memset (image->data(i), 0, pixels * sizeof(int));
416         }
417         return image;
418 }
419
420
421 shared_ptr<dcp::ReelAsset>
422 black_picture_asset (boost::filesystem::path dir, int frames)
423 {
424         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
425         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
426         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
427
428         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
429         boost::filesystem::create_directories (dir);
430         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
431         for (int i = 0; i < frames; ++i) {
432                 writer->write (frame.data().get(), frame.size());
433         }
434         writer->finalize ();
435
436         return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
437 }
438
439
440 BOOST_GLOBAL_FIXTURE (TestConfig);