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