Bv2.1 7.2.3: Check that subtitle <StartTime> exists and is 0.
[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.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 <asdcp/KM_util.h>
59 #include <asdcp/KM_prng.h>
60 #include <sndfile.h>
61 #include <libxml++/libxml++.h>
62 #include <boost/test/unit_test.hpp>
63 #include <cstdio>
64 #include <iostream>
65
66 using std::string;
67 using std::min;
68 using std::list;
69 using std::vector;
70 using std::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_tags, bool ignore_whitespace)
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_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
104                 return;
105         }
106
107         auto whitespace_content = [](xmlpp::Node* node) {
108                 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
109                 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
110         };
111
112         auto ref_children = ref->get_children ();
113         auto test_children = test->get_children ();
114
115         auto k = ref_children.begin ();
116         auto l = test_children.begin ();
117         while (k != ref_children.end() && l != test_children.end()) {
118
119                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
120                         ++k;
121                         continue;
122                 }
123
124                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
125                         ++l;
126                         continue;
127                 }
128
129                 if (whitespace_content(*k) && ignore_whitespace) {
130                         ++k;
131                         continue;
132                 }
133
134                 if (whitespace_content(*l) && ignore_whitespace) {
135                         ++l;
136                         continue;
137                 }
138
139                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
140
141                 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
142                 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
143                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
144                 if (ref_el && test_el) {
145                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
146                 }
147
148                 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
149                 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
150                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
151                 if (ref_cn && test_cn) {
152                         BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
153                 }
154
155                 ++k;
156                 ++l;
157         }
158
159         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
160                 ++k;
161         }
162
163         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
164                 ++l;
165         }
166
167         BOOST_REQUIRE (k == ref_children.end());
168         BOOST_REQUIRE (l == test_children.end());
169
170         auto ref_attributes = ref->get_attributes ();
171         auto test_attributes = test->get_attributes ();
172         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
173
174         auto m = ref_attributes.begin();
175         auto n = test_attributes.begin();
176         while (m != ref_attributes.end ()) {
177                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
178                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
179
180                 ++m;
181                 ++n;
182         }
183 }
184
185 void
186 check_xml (string ref, string test, list<string> ignore, bool ignore_whitespace)
187 {
188         xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
189         ref_parser->parse_memory (ref);
190         xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
191         xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
192         test_parser->parse_memory (test);
193         xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
194
195         check_xml (ref_root, test_root, ignore, ignore_whitespace);
196 }
197
198 void
199 check_file (boost::filesystem::path ref, boost::filesystem::path check)
200 {
201         uintmax_t size = boost::filesystem::file_size (ref);
202         BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
203         FILE* ref_file = dcp::fopen_boost (ref, "rb");
204         BOOST_REQUIRE (ref_file);
205         FILE* check_file = dcp::fopen_boost (check, "rb");
206         BOOST_REQUIRE (check_file);
207
208         int const buffer_size = 65536;
209         uint8_t* ref_buffer = new uint8_t[buffer_size];
210         uint8_t* check_buffer = new uint8_t[buffer_size];
211
212         uintmax_t pos = 0;
213
214         while (pos < size) {
215                 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
216                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
217                 BOOST_CHECK_EQUAL (r, this_time);
218                 r = fread (check_buffer, 1, this_time, check_file);
219                 BOOST_CHECK_EQUAL (r, this_time);
220
221                 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
222                         for (int i = 0; i < buffer_size; ++i) {
223                                 if (ref_buffer[i] != check_buffer[i]) {
224                                         BOOST_CHECK_MESSAGE (
225                                                 false,
226                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
227                                                 );
228                                         break;
229                                 }
230                         }
231                         break;
232                 }
233
234                 pos += this_time;
235         }
236
237         delete[] ref_buffer;
238         delete[] check_buffer;
239
240         fclose (ref_file);
241         fclose (check_file);
242 }
243
244
245 RNGFixer::RNGFixer ()
246 {
247         Kumu::cth_test = true;
248         Kumu::FortunaRNG().Reset();
249 }
250
251
252 RNGFixer::~RNGFixer ()
253 {
254         Kumu::cth_test = false;
255 }
256
257
258 shared_ptr<dcp::MonoPictureAsset>
259 simple_picture (boost::filesystem::path path, string suffix)
260 {
261         dcp::MXFMetadata mxf_meta;
262         mxf_meta.company_name = "OpenDCP";
263         mxf_meta.product_name = "OpenDCP";
264         mxf_meta.product_version = "0.0.25";
265
266         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
267         mp->set_metadata (mxf_meta);
268         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
269         dcp::ArrayData j2c ("test/data/flat_red.j2c");
270         for (int i = 0; i < 24; ++i) {
271                 picture_writer->write (j2c.data (), j2c.size ());
272         }
273         picture_writer->finalize ();
274
275         return mp;
276 }
277
278
279 shared_ptr<dcp::SoundAsset>
280 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language)
281 {
282         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
283         shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-US"), dcp::SMPTE));
284         ms->_language = language;
285         ms->set_metadata (mxf_meta);
286         vector<dcp::Channel> active_channels;
287         active_channels.push_back (dcp::LEFT);
288         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
289
290         SF_INFO info;
291         info.format = 0;
292         SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
293         BOOST_CHECK (sndfile);
294         float buffer[4096*6];
295         float* channels[1];
296         channels[0] = buffer;
297         while (true) {
298                 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
299                 sound_writer->write (channels, N);
300                 if (N < 4096) {
301                         break;
302                 }
303         }
304
305         sound_writer->finalize ();
306
307         return ms;
308 }
309
310
311 shared_ptr<dcp::DCP>
312 make_simple (boost::filesystem::path path, int reels)
313 {
314         /* Some known metadata */
315         dcp::MXFMetadata mxf_meta;
316         mxf_meta.company_name = "OpenDCP";
317         mxf_meta.product_name = "OpenDCP";
318         mxf_meta.product_version = "0.0.25";
319
320         boost::filesystem::remove_all (path);
321         boost::filesystem::create_directories (path);
322         shared_ptr<dcp::DCP> d (new dcp::DCP (path));
323         shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
324         cpl->set_annotation_text ("A Test DCP");
325         cpl->set_issuer ("OpenDCP 0.0.25");
326         cpl->set_creator ("OpenDCP 0.0.25");
327         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
328         cpl->set_content_version (
329                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
330                 );
331
332         for (int i = 0; i < reels; ++i) {
333                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
334
335                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix);
336                 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US");
337
338                 cpl->add (shared_ptr<dcp::Reel> (
339                                   new dcp::Reel (
340                                           shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
341                                           shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
342                                           )
343                                   ));
344         }
345
346         d->add (cpl);
347         return d;
348 }
349
350
351 shared_ptr<dcp::Subtitle>
352 simple_subtitle ()
353 {
354         return shared_ptr<dcp::Subtitle>(
355                 new dcp::SubtitleString(
356                         optional<string>(),
357                         false,
358                         false,
359                         false,
360                         dcp::Colour(255, 255, 255),
361                         42,
362                         1,
363                         dcp::Time(0, 0, 4, 0, 24),
364                         dcp::Time(0, 0, 8, 0, 24),
365                         0.5,
366                         dcp::HALIGN_CENTER,
367                         0.8,
368                         dcp::VALIGN_TOP,
369                         dcp::DIRECTION_LTR,
370                         "Hello world",
371                         dcp::NONE,
372                         dcp::Colour(255, 255, 255),
373                         dcp::Time(),
374                         dcp::Time()
375                         )
376                 );
377 }
378
379
380 shared_ptr<dcp::DCP>
381 make_simple_with_interop_subs (boost::filesystem::path path)
382 {
383         shared_ptr<dcp::DCP> dcp = make_simple (path);
384
385         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
386         subs->add (simple_subtitle());
387
388         boost::filesystem::create_directory (path / "subs");
389         dcp::ArrayData data(4096);
390         subs->add_font ("afont", data);
391         subs->write (path / "subs" / "subs.xml");
392
393         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
394         dcp->cpls().front()->reels().front()->add (reel_subs);
395
396         return dcp;
397 }
398
399
400 shared_ptr<dcp::DCP>
401 make_simple_with_smpte_subs (boost::filesystem::path path)
402 {
403         shared_ptr<dcp::DCP> dcp = make_simple (path);
404
405         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
406         subs->set_language (dcp::LanguageTag("de-DE"));
407         subs->set_start_time (dcp::Time());
408         subs->add (simple_subtitle());
409
410         subs->write (path / "subs.mxf");
411
412         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
413         dcp->cpls().front()->reels().front()->add (reel_subs);
414
415         return dcp;
416 }
417
418
419 shared_ptr<dcp::DCP>
420 make_simple_with_interop_ccaps (boost::filesystem::path path)
421 {
422         shared_ptr<dcp::DCP> dcp = make_simple (path);
423
424         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
425         subs->add (simple_subtitle());
426         subs->write (path / "ccap.xml");
427
428         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
429         dcp->cpls().front()->reels().front()->add (reel_caps);
430
431         return dcp;
432 }
433
434
435 shared_ptr<dcp::DCP>
436 make_simple_with_smpte_ccaps (boost::filesystem::path path)
437 {
438         shared_ptr<dcp::DCP> dcp = make_simple (path);
439
440         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
441         subs->set_language (dcp::LanguageTag("de-DE"));
442         subs->set_start_time (dcp::Time());
443         subs->add (simple_subtitle());
444         subs->write (path / "ccap.mxf");
445
446         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
447         dcp->cpls().front()->reels().front()->add (reel_caps);
448
449         return dcp;
450 }
451
452
453 shared_ptr<dcp::OpenJPEGImage>
454 black_image (dcp::Size size)
455 {
456         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
457         int const pixels = size.width * size.height;
458         for (int i = 0; i < 3; ++i) {
459                 memset (image->data(i), 0, pixels * sizeof(int));
460         }
461         return image;
462 }
463
464
465 shared_ptr<dcp::ReelAsset>
466 black_picture_asset (boost::filesystem::path dir, int frames)
467 {
468         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
469         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
470         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
471
472         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
473         boost::filesystem::create_directories (dir);
474         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
475         for (int i = 0; i < frames; ++i) {
476                 writer->write (frame.data(), frame.size());
477         }
478         writer->finalize ();
479
480         return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
481 }
482
483
484 BOOST_GLOBAL_FIXTURE (TestConfig);