Bv2.1 6.2.1: Check that the sound MXF Language tag conforms to RFC 5646.
[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 std::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         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, list<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 N = boost::filesystem::file_size (ref);
203         BOOST_CHECK_EQUAL (N, 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         string error;
214         error = "File " + check.string() + " differs from reference " + ref.string();
215
216         while (N) {
217                 uintmax_t this_time = min (uintmax_t (buffer_size), N);
218                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
219                 BOOST_CHECK_EQUAL (r, this_time);
220                 r = fread (check_buffer, 1, this_time, check_file);
221                 BOOST_CHECK_EQUAL (r, this_time);
222
223                 BOOST_CHECK_MESSAGE (memcmp (ref_buffer, check_buffer, this_time) == 0, error);
224                 if (memcmp (ref_buffer, check_buffer, this_time)) {
225                         break;
226                 }
227
228                 N -= this_time;
229         }
230
231         delete[] ref_buffer;
232         delete[] check_buffer;
233
234         fclose (ref_file);
235         fclose (check_file);
236 }
237
238
239 RNGFixer::RNGFixer ()
240 {
241         Kumu::cth_test = true;
242         Kumu::FortunaRNG().Reset();
243 }
244
245
246 RNGFixer::~RNGFixer ()
247 {
248         Kumu::cth_test = false;
249 }
250
251
252 shared_ptr<dcp::MonoPictureAsset>
253 simple_picture (boost::filesystem::path path, string suffix)
254 {
255         dcp::MXFMetadata mxf_meta;
256         mxf_meta.company_name = "OpenDCP";
257         mxf_meta.product_name = "OpenDCP";
258         mxf_meta.product_version = "0.0.25";
259
260         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
261         mp->set_metadata (mxf_meta);
262         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
263         dcp::File j2c ("test/data/32x32_red_square.j2c");
264         for (int i = 0; i < 24; ++i) {
265                 picture_writer->write (j2c.data (), j2c.size ());
266         }
267         picture_writer->finalize ();
268
269         return mp;
270 }
271
272
273 shared_ptr<dcp::SoundAsset>
274 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language)
275 {
276         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
277         shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-US"), dcp::SMPTE));
278         ms->_language = language;
279         ms->set_metadata (mxf_meta);
280         vector<dcp::Channel> active_channels;
281         active_channels.push_back (dcp::LEFT);
282         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
283
284         SF_INFO info;
285         info.format = 0;
286         SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
287         BOOST_CHECK (sndfile);
288         float buffer[4096*6];
289         float* channels[1];
290         channels[0] = buffer;
291         while (true) {
292                 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
293                 sound_writer->write (channels, N);
294                 if (N < 4096) {
295                         break;
296                 }
297         }
298
299         sound_writer->finalize ();
300
301         return ms;
302 }
303
304
305 shared_ptr<dcp::DCP>
306 make_simple (boost::filesystem::path path, int reels)
307 {
308         /* Some known metadata */
309         dcp::MXFMetadata mxf_meta;
310         mxf_meta.company_name = "OpenDCP";
311         mxf_meta.product_name = "OpenDCP";
312         mxf_meta.product_version = "0.0.25";
313
314         boost::filesystem::remove_all (path);
315         boost::filesystem::create_directories (path);
316         shared_ptr<dcp::DCP> d (new dcp::DCP (path));
317         shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
318         cpl->set_annotation_text ("A Test DCP");
319         cpl->set_issuer ("OpenDCP 0.0.25");
320         cpl->set_creator ("OpenDCP 0.0.25");
321         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
322         cpl->set_content_version (
323                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
324                 );
325
326         for (int i = 0; i < reels; ++i) {
327                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
328
329                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix);
330                 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US");
331
332                 cpl->add (shared_ptr<dcp::Reel> (
333                                   new dcp::Reel (
334                                           shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
335                                           shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
336                                           )
337                                   ));
338         }
339
340         d->add (cpl);
341         return d;
342 }
343
344
345 shared_ptr<dcp::Subtitle>
346 simple_subtitle ()
347 {
348         return shared_ptr<dcp::Subtitle>(
349                 new dcp::SubtitleString(
350                         optional<string>(),
351                         false,
352                         false,
353                         false,
354                         dcp::Colour(255, 255, 255),
355                         42,
356                         1,
357                         dcp::Time(0, 0, 4, 0, 24),
358                         dcp::Time(0, 0, 8, 0, 24),
359                         0.5,
360                         dcp::HALIGN_CENTER,
361                         0.8,
362                         dcp::VALIGN_TOP,
363                         dcp::DIRECTION_LTR,
364                         "Hello world",
365                         dcp::NONE,
366                         dcp::Colour(255, 255, 255),
367                         dcp::Time(),
368                         dcp::Time()
369                         )
370                 );
371 }
372
373
374 shared_ptr<dcp::DCP>
375 make_simple_with_interop_subs (boost::filesystem::path path)
376 {
377         shared_ptr<dcp::DCP> dcp = make_simple (path);
378
379         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
380         subs->add (simple_subtitle());
381
382         boost::filesystem::create_directory (path / "subs");
383         dcp::ArrayData data(4096);
384         subs->add_font ("afont", data);
385         subs->write (path / "subs" / "subs.xml");
386
387         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
388         dcp->cpls().front()->reels().front()->add (reel_subs);
389
390         return dcp;
391 }
392
393
394 shared_ptr<dcp::DCP>
395 make_simple_with_smpte_subs (boost::filesystem::path path)
396 {
397         shared_ptr<dcp::DCP> dcp = make_simple (path);
398
399         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
400         subs->add (simple_subtitle());
401
402         dcp::ArrayData data(4096);
403         subs->write (path / "subs.mxf");
404
405         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
406         dcp->cpls().front()->reels().front()->add (reel_subs);
407
408         return dcp;
409 }
410
411
412 shared_ptr<dcp::DCP>
413 make_simple_with_interop_ccaps (boost::filesystem::path path)
414 {
415         shared_ptr<dcp::DCP> dcp = make_simple (path);
416
417         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
418         subs->add (simple_subtitle());
419         subs->write (path / "ccap.xml");
420
421         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
422         dcp->cpls().front()->reels().front()->add (reel_caps);
423
424         return dcp;
425 }
426
427
428 shared_ptr<dcp::DCP>
429 make_simple_with_smpte_ccaps (boost::filesystem::path path)
430 {
431         shared_ptr<dcp::DCP> dcp = make_simple (path);
432
433         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
434         subs->add (simple_subtitle());
435         subs->write (path / "ccap.mxf");
436
437         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
438         dcp->cpls().front()->reels().front()->add (reel_caps);
439
440         return dcp;
441 }
442
443
444 shared_ptr<dcp::OpenJPEGImage>
445 black_image ()
446 {
447         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
448         int const pixels = 1998 * 1080;
449         for (int i = 0; i < 3; ++i) {
450                 memset (image->data(i), 0, pixels * sizeof(int));
451         }
452         return image;
453 }
454
455
456 shared_ptr<dcp::ReelAsset>
457 black_picture_asset (boost::filesystem::path dir, int frames)
458 {
459         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
460         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
461         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
462
463         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
464         boost::filesystem::create_directories (dir);
465         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
466         for (int i = 0; i < frames; ++i) {
467                 writer->write (frame.data(), frame.size());
468         }
469         writer->finalize ();
470
471         return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
472 }
473
474
475 BOOST_GLOBAL_FIXTURE (TestConfig);