Test churn from adding make_uuid() into CPL.
[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 "j2k_transcode.h"
41 #include "mono_picture_asset.h"
42 #include "mono_picture_asset.h"
43 #include "openjpeg_image.h"
44 #include "picture_asset_writer.h"
45 #include "picture_asset_writer.h"
46 #include "reel.h"
47 #include "reel_asset.h"
48 #include "reel_interop_closed_caption_asset.h"
49 #include "reel_interop_subtitle_asset.h"
50 #include "reel_markers_asset.h"
51 #include "reel_mono_picture_asset.h"
52 #include "reel_mono_picture_asset.h"
53 #include "reel_smpte_closed_caption_asset.h"
54 #include "reel_smpte_subtitle_asset.h"
55 #include "reel_sound_asset.h"
56 #include "smpte_subtitle_asset.h"
57 #include "sound_asset.h"
58 #include "sound_asset_writer.h"
59 #include "test.h"
60 #include "util.h"
61 #include "warnings.h"
62 LIBDCP_DISABLE_WARNINGS
63 #include <asdcp/KM_util.h>
64 #include <asdcp/KM_prng.h>
65 LIBDCP_ENABLE_WARNINGS
66 #include <sndfile.h>
67 LIBDCP_DISABLE_WARNINGS
68 #include <libxml++/libxml++.h>
69 LIBDCP_ENABLE_WARNINGS
70 #include <boost/test/unit_test.hpp>
71 #include <cstdio>
72 #include <iostream>
73
74
75 using std::string;
76 using std::min;
77 using std::vector;
78 using std::shared_ptr;
79 using std::make_shared;
80 using boost::optional;
81
82
83 boost::filesystem::path private_test;
84 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
85
86
87 struct TestConfig
88 {
89         TestConfig()
90         {
91                 dcp::init ();
92                 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
93                         private_test = boost::unit_test::framework::master_test_suite().argv[1];
94                 }
95
96                 using namespace boost::filesystem;
97                 boost::system::error_code ec;
98                 remove_all (xsd_test, ec);
99                 boost::filesystem::create_directory (xsd_test);
100                 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
101                         copy_file (*i, xsd_test / i->path().filename());
102                 }
103         }
104 };
105
106
107 void
108 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
109 {
110         BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
111         BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
112
113         if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
114                 return;
115         }
116
117         auto whitespace_content = [](xmlpp::Node* node) {
118                 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
119                 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
120         };
121
122         auto ref_children = ref->get_children ();
123         auto test_children = test->get_children ();
124
125         auto k = ref_children.begin ();
126         auto l = test_children.begin ();
127         while (k != ref_children.end() && l != test_children.end()) {
128
129                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
130                         ++k;
131                         continue;
132                 }
133
134                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
135                         ++l;
136                         continue;
137                 }
138
139                 if (whitespace_content(*k) && ignore_whitespace) {
140                         ++k;
141                         continue;
142                 }
143
144                 if (whitespace_content(*l) && ignore_whitespace) {
145                         ++l;
146                         continue;
147                 }
148
149                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
150
151                 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
152                 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
153                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
154                 if (ref_el && test_el) {
155                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
156                 }
157
158                 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
159                 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
160                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
161                 if (ref_cn && test_cn) {
162                         BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
163                 }
164
165                 ++k;
166                 ++l;
167         }
168
169         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
170                 ++k;
171         }
172
173         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
174                 ++l;
175         }
176
177         BOOST_REQUIRE (k == ref_children.end());
178         BOOST_REQUIRE (l == test_children.end());
179
180         auto ref_attributes = ref->get_attributes ();
181         auto test_attributes = test->get_attributes ();
182         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
183
184         auto m = ref_attributes.begin();
185         auto n = test_attributes.begin();
186         while (m != ref_attributes.end ()) {
187                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
188                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
189
190                 ++m;
191                 ++n;
192         }
193 }
194
195 void
196 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
197 {
198         auto ref_parser = new xmlpp::DomParser ();
199         ref_parser->parse_memory (ref);
200         auto ref_root = ref_parser->get_document()->get_root_node ();
201         auto test_parser = new xmlpp::DomParser ();
202         test_parser->parse_memory (test);
203         auto test_root = test_parser->get_document()->get_root_node ();
204
205         check_xml (ref_root, test_root, ignore, ignore_whitespace);
206 }
207
208 void
209 check_file (boost::filesystem::path ref, boost::filesystem::path check)
210 {
211         uintmax_t size = boost::filesystem::file_size (ref);
212         BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
213         auto ref_file = dcp::fopen_boost (ref, "rb");
214         BOOST_REQUIRE (ref_file);
215         auto check_file = dcp::fopen_boost (check, "rb");
216         BOOST_REQUIRE (check_file);
217
218         int const buffer_size = 65536;
219         auto ref_buffer = new uint8_t[buffer_size];
220         auto check_buffer = new uint8_t[buffer_size];
221
222         uintmax_t pos = 0;
223
224         while (pos < size) {
225                 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
226                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
227                 BOOST_CHECK_EQUAL (r, this_time);
228                 r = fread (check_buffer, 1, this_time, check_file);
229                 BOOST_CHECK_EQUAL (r, this_time);
230
231                 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
232                         for (int i = 0; i < buffer_size; ++i) {
233                                 if (ref_buffer[i] != check_buffer[i]) {
234                                         BOOST_CHECK_MESSAGE (
235                                                 false,
236                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
237                                                 );
238                                         break;
239                                 }
240                         }
241                         break;
242                 }
243
244                 pos += this_time;
245         }
246
247         delete[] ref_buffer;
248         delete[] check_buffer;
249
250         fclose (ref_file);
251         fclose (check_file);
252 }
253
254
255 RNGFixer::RNGFixer ()
256 {
257         Kumu::cth_test = true;
258         Kumu::FortunaRNG().Reset();
259 }
260
261
262 RNGFixer::~RNGFixer ()
263 {
264         Kumu::cth_test = false;
265 }
266
267
268 shared_ptr<dcp::MonoPictureAsset>
269 simple_picture (boost::filesystem::path path, string suffix, int frames)
270 {
271         dcp::MXFMetadata mxf_meta;
272         mxf_meta.company_name = "OpenDCP";
273         mxf_meta.product_name = "OpenDCP";
274         mxf_meta.product_version = "0.0.25";
275
276         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::Standard::SMPTE));
277         mp->set_metadata (mxf_meta);
278         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
279
280         dcp::Size const size (1998, 1080);
281         auto image = make_shared<dcp::OpenJPEGImage>(size);
282         for (int i = 0; i < 3; ++i) {
283                 memset (image->data(i), 0, 2 * size.width * size.height);
284         }
285         auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
286
287         for (int i = 0; i < frames; ++i) {
288                 picture_writer->write (j2c.data(), j2c.size());
289         }
290         picture_writer->finalize ();
291
292         return mp;
293 }
294
295
296 shared_ptr<dcp::SoundAsset>
297 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
298 {
299         int const channels = 6;
300
301         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
302         shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE));
303         ms->_language = language;
304         ms->set_metadata (mxf_meta);
305         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
306
307         int const samples_per_frame = sample_rate / 24;
308
309         float* silence[channels];
310         for (auto i = 0; i < channels; ++i) {
311                 silence[i] = new float[samples_per_frame];
312                 memset (silence[i], 0, samples_per_frame * sizeof(float));
313         }
314
315         for (auto i = 0; i < frames; ++i) {
316                 sound_writer->write (silence, samples_per_frame);
317         }
318
319         sound_writer->finalize ();
320
321         for (auto i = 0; i < channels; ++i) {
322                 delete[] silence[i];
323         }
324
325         return ms;
326 }
327
328
329 shared_ptr<dcp::DCP>
330 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard)
331 {
332         /* Some known metadata */
333         dcp::MXFMetadata mxf_meta;
334         mxf_meta.company_name = "OpenDCP";
335         mxf_meta.product_name = "OpenDCP";
336         mxf_meta.product_version = "0.0.25";
337
338         boost::filesystem::remove_all (path);
339         boost::filesystem::create_directories (path);
340         auto d = make_shared<dcp::DCP>(path);
341         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
342         cpl->set_annotation_text ("A Test DCP");
343         cpl->set_issuer ("OpenDCP 0.0.25");
344         cpl->set_creator ("OpenDCP 0.0.25");
345         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
346         cpl->set_content_version (
347                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
348                 );
349         cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
350         cpl->set_main_sound_sample_rate(48000);
351         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
352         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
353         cpl->set_version_number(1);
354
355         for (int i = 0; i < reels; ++i) {
356                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
357
358                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
359                 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
360
361                 auto reel = make_shared<dcp::Reel>(
362                         shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
363                         shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
364                         );
365
366                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
367                 if (i == 0) {
368                         markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
369                 }
370                 if (i == reels - 1) {
371                         markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
372                 }
373                 reel->add (markers);
374
375                 cpl->add (reel);
376         }
377
378         d->add (cpl);
379         return d;
380 }
381
382
383 shared_ptr<dcp::Subtitle>
384 simple_subtitle ()
385 {
386         return make_shared<dcp::SubtitleString>(
387                 optional<string>(),
388                 false,
389                 false,
390                 false,
391                 dcp::Colour(255, 255, 255),
392                 42,
393                 1,
394                 dcp::Time(0, 0, 4, 0, 24),
395                 dcp::Time(0, 0, 8, 0, 24),
396                 0.5,
397                 dcp::HAlign::CENTER,
398                 0.8,
399                 dcp::VAlign::TOP,
400                 dcp::Direction::LTR,
401                 "Hello world",
402                 dcp::Effect::NONE,
403                 dcp::Colour(255, 255, 255),
404                 dcp::Time(),
405                 dcp::Time()
406                 );
407 }
408
409
410 shared_ptr<dcp::ReelMarkersAsset>
411 simple_markers (int frames)
412 {
413         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames, 0);
414         markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
415         markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
416         return markers;
417 }
418
419
420 shared_ptr<dcp::DCP>
421 make_simple_with_interop_subs (boost::filesystem::path path)
422 {
423         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
424
425         auto subs = make_shared<dcp::InteropSubtitleAsset>();
426         subs->add (simple_subtitle());
427
428         boost::filesystem::create_directory (path / "subs");
429         dcp::ArrayData data(4096);
430         subs->add_font ("afont", data);
431         subs->write (path / "subs" / "subs.xml");
432
433         auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
434         dcp->cpls().front()->reels().front()->add (reel_subs);
435
436         return dcp;
437 }
438
439
440 shared_ptr<dcp::DCP>
441 make_simple_with_smpte_subs (boost::filesystem::path path)
442 {
443         auto dcp = make_simple (path, 1, 192);
444
445         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
446         subs->set_language (dcp::LanguageTag("de-DE"));
447         subs->set_start_time (dcp::Time());
448         subs->add (simple_subtitle());
449
450         subs->write (path / "subs.mxf");
451
452         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
453         dcp->cpls().front()->reels().front()->add (reel_subs);
454
455         return dcp;
456 }
457
458
459 shared_ptr<dcp::DCP>
460 make_simple_with_interop_ccaps (boost::filesystem::path path)
461 {
462         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
463
464         auto subs = make_shared<dcp::InteropSubtitleAsset>();
465         subs->add (simple_subtitle());
466         subs->write (path / "ccap.xml");
467
468         auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
469         dcp->cpls()[0]->reels()[0]->add (reel_caps);
470
471         return dcp;
472 }
473
474
475 shared_ptr<dcp::DCP>
476 make_simple_with_smpte_ccaps (boost::filesystem::path path)
477 {
478         auto dcp = make_simple (path, 1, 192);
479
480         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
481         subs->set_language (dcp::LanguageTag("de-DE"));
482         subs->set_start_time (dcp::Time());
483         subs->add (simple_subtitle());
484         subs->write (path / "ccap.mxf");
485
486         auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
487         dcp->cpls()[0]->reels()[0]->add(reel_caps);
488
489         return dcp;
490 }
491
492
493 shared_ptr<dcp::OpenJPEGImage>
494 black_image (dcp::Size size)
495 {
496         auto image = make_shared<dcp::OpenJPEGImage>(size);
497         int const pixels = size.width * size.height;
498         for (int i = 0; i < 3; ++i) {
499                 memset (image->data(i), 0, pixels * sizeof(int));
500         }
501         return image;
502 }
503
504
505 shared_ptr<dcp::ReelAsset>
506 black_picture_asset (boost::filesystem::path dir, int frames)
507 {
508         auto image = black_image ();
509         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
510         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
511
512         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
513         asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
514         boost::filesystem::create_directories (dir);
515         auto writer = asset->start_write (dir / "pic.mxf", true);
516         for (int i = 0; i < frames; ++i) {
517                 writer->write (frame.data(), frame.size());
518         }
519         writer->finalize ();
520
521         return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
522 }
523
524
525 boost::filesystem::path
526 find_file (boost::filesystem::path dir, string filename_part)
527 {
528         boost::optional<boost::filesystem::path> found;
529         for (auto i: boost::filesystem::directory_iterator(dir)) {
530                 if (i.path().filename().string().find(filename_part) != string::npos) {
531                         BOOST_REQUIRE (!found);
532                         found = i;
533                 }
534         }
535         BOOST_REQUIRE (found);
536         return *found;
537 }
538
539
540 BOOST_GLOBAL_FIXTURE (TestConfig);