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