C++11 tidying.
[libdcp.git] / test / dcp_test.cc
1 /*
2     Copyright (C) 2013-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 #include "dcp.h"
35 #include "metadata.h"
36 #include "cpl.h"
37 #include "mono_picture_asset.h"
38 #include "stereo_picture_asset.h"
39 #include "picture_asset_writer.h"
40 #include "reel_picture_asset.h"
41 #include "sound_asset_writer.h"
42 #include "sound_asset.h"
43 #include "atmos_asset.h"
44 #include "reel.h"
45 #include "test.h"
46 #include "reel_mono_picture_asset.h"
47 #include "reel_stereo_picture_asset.h"
48 #include "reel_sound_asset.h"
49 #include "reel_atmos_asset.h"
50 #include "reel_markers_asset.h"
51 #include <asdcp/KM_util.h>
52 #include <sndfile.h>
53 #include <boost/test/unit_test.hpp>
54
55 using std::string;
56 using std::vector;
57 using std::dynamic_pointer_cast;
58 using std::shared_ptr;
59 using std::make_shared;
60 #if BOOST_VERSION >= 106100
61 using namespace boost::placeholders;
62 #endif
63
64
65 /** Test creation of a 2D SMPTE DCP from very simple inputs */
66 BOOST_AUTO_TEST_CASE (dcp_test1)
67 {
68         RNGFixer fixer;
69
70         make_simple("build/test/DCP/dcp_test1")->write_xml(
71                 "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "A Test DCP"
72                 );
73
74         /* build/test/DCP/dcp_test1 is checked against test/ref/DCP/dcp_test1 by run/tests */
75 }
76
77 /** Test creation of a 3D DCP from very simple inputs */
78 BOOST_AUTO_TEST_CASE (dcp_test2)
79 {
80         RNGFixer fix;
81
82         /* Some known metadata */
83         dcp::MXFMetadata mxf_meta;
84         mxf_meta.company_name = "OpenDCP";
85         mxf_meta.product_name = "OpenDCP";
86         mxf_meta.product_version = "0.0.25";
87
88         /* We're making build/test/DCP/dcp_test2 */
89         boost::filesystem::remove_all ("build/test/DCP/dcp_test2");
90         boost::filesystem::create_directories ("build/test/DCP/dcp_test2");
91         dcp::DCP d ("build/test/DCP/dcp_test2");
92         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
93         cpl->set_content_version (
94                 dcp::ContentVersion("urn:uri:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00", "81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00")
95                 );
96         cpl->set_issuer ("OpenDCP 0.0.25");
97         cpl->set_creator ("OpenDCP 0.0.25");
98         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
99         cpl->set_annotation_text ("A Test DCP");
100
101         auto mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
102         mp->set_metadata (mxf_meta);
103         auto picture_writer = mp->start_write ("build/test/DCP/dcp_test2/video.mxf", false);
104         dcp::ArrayData j2c ("test/data/flat_red.j2c");
105         for (int i = 0; i < 24; ++i) {
106                 /* Left */
107                 picture_writer->write (j2c.data (), j2c.size ());
108                 /* Right */
109                 picture_writer->write (j2c.data (), j2c.size ());
110         }
111         picture_writer->finalize ();
112
113         auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::Standard::SMPTE);
114         ms->set_metadata (mxf_meta);
115         auto sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf");
116
117         SF_INFO info;
118         info.format = 0;
119         auto sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
120         BOOST_CHECK (sndfile);
121         float buffer[4096*6];
122         float* channels[1];
123         channels[0] = buffer;
124         while (true) {
125                 auto N = sf_readf_float (sndfile, buffer, 4096);
126                 sound_writer->write (channels, N);
127                 if (N < 4096) {
128                         break;
129                 }
130         }
131
132         sound_writer->finalize ();
133
134         cpl->add (make_shared<dcp::Reel>(
135                           make_shared<dcp::ReelStereoPictureAsset>(mp, 0),
136                           make_shared<dcp::ReelSoundAsset>(ms, 0)
137                           ));
138
139         d.add (cpl);
140
141         d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "Created by libdcp");
142
143         /* build/test/DCP/dcp_test2 is checked against test/ref/DCP/dcp_test2 by run/tests */
144 }
145
146 static void
147 note (dcp::NoteType, string)
148 {
149
150 }
151
152 /** Test comparison of a DCP with itself */
153 BOOST_AUTO_TEST_CASE (dcp_test3)
154 {
155         dcp::DCP A ("test/ref/DCP/dcp_test1");
156         A.read ();
157         dcp::DCP B ("test/ref/DCP/dcp_test1");
158         B.read ();
159
160         BOOST_CHECK (A.equals (B, dcp::EqualityOptions(), boost::bind (&note, _1, _2)));
161 }
162
163 /** Test comparison of a DCP with a different DCP */
164 BOOST_AUTO_TEST_CASE (dcp_test4)
165 {
166         dcp::DCP A ("test/ref/DCP/dcp_test1");
167         A.read ();
168         dcp::DCP B ("test/ref/DCP/dcp_test2");
169         B.read ();
170
171         BOOST_CHECK (!A.equals (B, dcp::EqualityOptions(), boost::bind (&note, _1, _2)));
172 }
173
174 static
175 void
176 test_rewriting_sound(string name, bool modify)
177 {
178         using namespace boost::filesystem;
179
180         dcp::DCP A ("test/ref/DCP/dcp_test1");
181         A.read ();
182
183         BOOST_REQUIRE (!A.cpls().empty());
184         BOOST_REQUIRE (!A.cpls().front()->reels().empty());
185         auto A_picture = dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(A.cpls().front()->reels().front()->main_picture());
186         BOOST_REQUIRE (A_picture);
187         auto A_sound = dynamic_pointer_cast<dcp::ReelSoundAsset>(A.cpls().front()->reels().front()->main_sound());
188
189         string const picture = "j2c_5279f9aa-94d7-42a6-b0e0-e4eaec4e2a15.mxf";
190
191         remove_all ("build/test/" + name);
192         dcp::DCP B ("build/test/" + name);
193         auto reel = make_shared<dcp::Reel>();
194
195         BOOST_REQUIRE (A_picture->mono_asset());
196         BOOST_REQUIRE (A_picture->mono_asset()->file());
197         copy_file (A_picture->mono_asset()->file().get(), path("build") / "test" / name / picture);
198         reel->add(make_shared<dcp::ReelMonoPictureAsset>(make_shared<dcp::MonoPictureAsset>(path("build") / "test" / name / picture), 0));
199
200         auto reader = A_sound->asset()->start_read();
201         auto sound = make_shared<dcp::SoundAsset>(A_sound->asset()->edit_rate(), A_sound->asset()->sampling_rate(), A_sound->asset()->channels(), dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
202         auto writer = sound->start_write(path("build") / "test" / name / "pcm_8246f87f-e1df-4c42-a290-f3b3069ff021.mxf", {});
203
204         bool need_to_modify = modify;
205         for (int i = 0; i < A_sound->asset()->intrinsic_duration(); ++i) {
206                 auto sf = reader->get_frame (i);
207                 float* out[sf->channels()];
208                 for (int j = 0; j < sf->channels(); ++j) {
209                         out[j] = new float[sf->samples()];
210                 }
211                 for (int j = 0; j < sf->samples(); ++j) {
212                         for (int k = 0; k < sf->channels(); ++k) {
213                                 out[k][j] = static_cast<float>(sf->get(k, j)) / (1 << 23);
214                                 if (need_to_modify) {
215                                         out[k][j] += 1.0 / (1 << 23);
216                                         need_to_modify = false;
217                                 }
218                         }
219                 }
220                 writer->write (out, sf->samples());
221                 for (int j = 0; j < sf->channels(); ++j) {
222                         delete[] out[j];
223                 }
224         }
225         writer->finalize();
226
227         reel->add(make_shared<dcp::ReelSoundAsset>(sound, 0));
228         reel->add(simple_markers());
229
230         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
231         cpl->add (reel);
232
233         B.add (cpl);
234         B.write_xml ();
235
236         dcp::EqualityOptions eq;
237         eq.reel_hashes_can_differ = true;
238         eq.max_audio_sample_error = 0;
239         if (modify) {
240                 BOOST_CHECK (!A.equals(B, eq, boost::bind(&note, _1, _2)));
241         } else {
242                 BOOST_CHECK (A.equals(B, eq, boost::bind(&note, _1, _2)));
243         }
244 }
245
246 /** Test comparison of a DCP with another that has the same picture and the same (but re-written) sound */
247 BOOST_AUTO_TEST_CASE (dcp_test9)
248 {
249         test_rewriting_sound ("dcp_test9", false);
250 }
251
252 /** Test comparison of a DCP with another that has the same picture and very slightly modified sound */
253 BOOST_AUTO_TEST_CASE (dcp_test10)
254 {
255         test_rewriting_sound ("dcp_test10", true);
256 }
257
258 /** Test creation of a 2D DCP with an Atmos track */
259 BOOST_AUTO_TEST_CASE (dcp_test5)
260 {
261         RNGFixer fix;
262
263         /* Some known metadata */
264         dcp::MXFMetadata mxf_meta;
265         mxf_meta.company_name = "OpenDCP";
266         mxf_meta.product_name = "OpenDCP";
267         mxf_meta.product_version = "0.0.25";
268
269         /* We're making build/test/DCP/dcp_test5 */
270         boost::filesystem::remove_all ("build/test/DCP/dcp_test5");
271         boost::filesystem::create_directories ("build/test/DCP/dcp_test5");
272         dcp::DCP d ("build/test/DCP/dcp_test5");
273         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
274         cpl->set_content_version (
275                 dcp::ContentVersion("urn:uri:81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00", "81fb54df-e1bf-4647-8788-ea7ba154375b_2012-07-17T04:45:18+00:00")
276                 );
277         cpl->set_issuer ("OpenDCP 0.0.25");
278         cpl->set_creator ("OpenDCP 0.0.25");
279         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
280         cpl->set_annotation_text ("A Test DCP");
281
282         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
283         mp->set_metadata (mxf_meta);
284         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write ("build/test/DCP/dcp_test5/video.mxf", false);
285         dcp::ArrayData j2c ("test/data/flat_red.j2c");
286         for (int i = 0; i < 24; ++i) {
287                 picture_writer->write (j2c.data (), j2c.size ());
288         }
289         picture_writer->finalize ();
290
291         auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::Standard::SMPTE);
292         ms->set_metadata (mxf_meta);
293         auto sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf");
294
295         SF_INFO info;
296         info.format = 0;
297         SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
298         BOOST_CHECK (sndfile);
299         float buffer[4096*6];
300         float* channels[1];
301         channels[0] = buffer;
302         while (true) {
303                 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
304                 sound_writer->write (channels, N);
305                 if (N < 4096) {
306                         break;
307                 }
308         }
309
310         sound_writer->finalize ();
311
312         auto am = make_shared<dcp::AtmosAsset>(private_test / "20160218_NameOfFilm_FTR_OV_EN_A_dcs_r01.mxf");
313
314         cpl->add(make_shared<dcp::Reel>(
315                         make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
316                         make_shared<dcp::ReelSoundAsset>(ms, 0),
317                         shared_ptr<dcp::ReelSubtitleAsset>(),
318                         shared_ptr<dcp::ReelMarkersAsset>(),
319                         make_shared<dcp::ReelAtmosAsset>(am, 0)
320                         ));
321
322         d.add (cpl);
323
324         d.write_xml ("OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "Created by libdcp");
325
326         /* build/test/DCP/dcp_test5 is checked against test/ref/DCP/dcp_test5 by run/tests */
327 }
328
329 /** Basic tests of reading a 2D DCP with an Atmos track */
330 BOOST_AUTO_TEST_CASE (dcp_test6)
331 {
332         dcp::DCP dcp ("test/ref/DCP/dcp_test5");
333         dcp.read ();
334
335         BOOST_REQUIRE_EQUAL (dcp.cpls().size(), 1U);
336         BOOST_REQUIRE_EQUAL (dcp.cpls()[0]->reels().size(), 1U);
337         BOOST_CHECK (dcp.cpls().front()->reels().front()->main_picture());
338         BOOST_CHECK (dcp.cpls().front()->reels().front()->main_sound());
339         BOOST_CHECK (!dcp.cpls().front()->reels().front()->main_subtitle());
340         BOOST_CHECK (dcp.cpls().front()->reels().front()->atmos());
341 }
342
343 /** Test creation of a 2D Interop DCP from very simple inputs */
344 BOOST_AUTO_TEST_CASE (dcp_test7)
345 {
346         RNGFixer fix;
347
348         make_simple("build/test/DCP/dcp_test7", 1, 24, dcp::Standard::INTEROP)->write_xml(
349                 "OpenDCP 0.0.25", "OpenDCP 0.0.25", "2012-07-17T04:45:18+00:00", "Created by libdcp"
350                 );
351
352         /* build/test/DCP/dcp_test7 is checked against test/ref/DCP/dcp_test7 by run/tests */
353 }
354
355 /** Test reading of a DCP with multiple CPLs */
356 BOOST_AUTO_TEST_CASE (dcp_test8)
357 {
358         dcp::DCP dcp (private_test / "data/SMPTE_TST-B1PB2P_S_EN-EN-CCAP_5171-HI-VI_2K_ISDCF_20151123_DPPT_SMPTE_combo/");
359         dcp.read ();
360
361         BOOST_REQUIRE_EQUAL (dcp.cpls().size(), 2U);
362 }
363
364
365 /** Test reading a DCP whose ASSETMAP contains assets not used by any PKL */
366 BOOST_AUTO_TEST_CASE (dcp_things_in_assetmap_not_in_pkl)
367 {
368         dcp::DCP dcp ("test/data/extra_assetmap");
369         BOOST_CHECK_NO_THROW (dcp.read());
370 }
371
372
373 /** Test that writing the XML for a DCP with no CPLs throws */
374 BOOST_AUTO_TEST_CASE (dcp_with_no_cpls)
375 {
376         dcp::DCP dcp ("build/test/dcp_with_no_cpls");
377         BOOST_REQUIRE_THROW (dcp.write_xml(), dcp::MiscError);
378 }
379
380
381 /** Test that writing the XML for a DCP with Interop CPLs makes a SMPTE assetmap */
382 BOOST_AUTO_TEST_CASE (dcp_with_interop_cpls)
383 {
384         boost::filesystem::path path = "build/test/dcp_with_interop_cpls";
385         boost::filesystem::remove_all (path);
386         dcp::DCP dcp (path);
387         auto cpl1 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP);
388         cpl1->add(make_shared<dcp::Reel>());
389         dcp.add(cpl1);
390         auto cpl2 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP);
391         cpl2->add(make_shared<dcp::Reel>());
392         dcp.add(cpl2);
393         dcp.write_xml ();
394         BOOST_REQUIRE (boost::filesystem::exists(path / "ASSETMAP"));
395         BOOST_REQUIRE (!boost::filesystem::exists(path / "ASSETMAP.xml"));
396 }
397
398
399 /** Test that writing the XML for a DCP with SMPTE CPLs makes a SMPTE assetmap */
400 BOOST_AUTO_TEST_CASE (dcp_with_smpte_cpls)
401 {
402         boost::filesystem::path path = "build/test/dcp_with_smpte_cpls";
403         boost::filesystem::remove_all (path);
404         dcp::DCP dcp (path);
405         auto cpl1 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
406         cpl1->add(make_shared<dcp::Reel>());
407         dcp.add(cpl1);
408         auto cpl2 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
409         cpl2->add(make_shared<dcp::Reel>());
410         dcp.add(cpl2);
411         dcp.write_xml ();
412         BOOST_REQUIRE (!boost::filesystem::exists(path / "ASSETMAP"));
413         BOOST_REQUIRE (boost::filesystem::exists(path / "ASSETMAP.xml"));
414 }
415
416
417 /** Test that writing the XML for a DCP with mixed-standard CPLs throws */
418 BOOST_AUTO_TEST_CASE (dcp_with_mixed_cpls)
419 {
420         dcp::DCP dcp ("build/test/dcp_with_mixed_cpls");
421         dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE));
422         dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP));
423         dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE));
424         BOOST_REQUIRE_THROW (dcp.write_xml(), dcp::MiscError);
425 }
426
427
428 BOOST_AUTO_TEST_CASE (dcp_add_kdm_test)
429 {
430         /* Some CPLs, each with a reel */
431
432         shared_ptr<dcp::CPL> cpls[] = {
433                 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE),
434                 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE),
435                 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE)
436         };
437
438         shared_ptr<dcp::Reel> reels[] = {
439                 make_shared<dcp::Reel>(),
440                 make_shared<dcp::Reel>(),
441                 make_shared<dcp::Reel>()
442         };
443
444         for (auto i = 0; i < 3; ++i) {
445                 cpls[i]->add(reels[i]);
446         }
447
448         dcp::DCP dcp ("build/test/dcp_add_kdm_test");
449         dcp.add(cpls[0]);
450         dcp.add(cpls[1]);
451         dcp.add(cpls[2]);
452
453         /* Simple KDM with one key that should be given to cpls[0] */
454
455         auto kdm_1 = dcp::DecryptedKDM({}, {}, "", "", "");
456         auto kdm_1_uuid = dcp::make_uuid();
457         kdm_1.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_1_uuid, dcp::Key(), cpls[0]->id(), dcp::Standard::SMPTE));
458         dcp.add (kdm_1);
459         BOOST_REQUIRE_EQUAL (reels[0]->_kdms.size(), 1U);
460         BOOST_CHECK_EQUAL (reels[0]->_kdms[0].keys().size(), 1U);
461         BOOST_CHECK_EQUAL (reels[0]->_kdms[0].keys()[0].id(), kdm_1_uuid);
462         BOOST_CHECK_EQUAL (reels[1]->_kdms.size(), 0U);
463         BOOST_CHECK_EQUAL (reels[2]->_kdms.size(), 0U);
464
465         /* KDM with two keys that should be given to cpls[1] and cpls[2] */
466
467         auto kdm_2 = dcp::DecryptedKDM({}, {}, "", "", "");
468         auto kdm_2_uuid_1 = dcp::make_uuid();
469         kdm_2.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_2_uuid_1, dcp::Key(), cpls[1]->id(), dcp::Standard::SMPTE));
470         auto kdm_2_uuid_2 = dcp::make_uuid();
471         kdm_2.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_2_uuid_2, dcp::Key(), cpls[2]->id(), dcp::Standard::SMPTE));
472         dcp.add (kdm_2);
473         /* Unchanged from previous test */
474         BOOST_CHECK (reels[0]->_kdms.size() == 1);
475         /* kdm_2 should have been added to both the other CPLs */
476         BOOST_REQUIRE_EQUAL (reels[1]->_kdms.size(), 1U);
477         BOOST_REQUIRE_EQUAL (reels[1]->_kdms[0].keys().size(), 2U);
478         BOOST_CHECK_EQUAL (reels[1]->_kdms[0].keys()[0].id(), kdm_2_uuid_1);
479         BOOST_CHECK_EQUAL (reels[1]->_kdms[0].keys()[1].id(), kdm_2_uuid_2);
480         BOOST_REQUIRE_EQUAL (reels[2]->_kdms.size(), 1U);
481         BOOST_REQUIRE_EQUAL (reels[2]->_kdms[0].keys().size(), 2U);
482         BOOST_CHECK_EQUAL (reels[2]->_kdms[0].keys()[0].id(), kdm_2_uuid_1);
483         BOOST_CHECK_EQUAL (reels[2]->_kdms[0].keys()[1].id(), kdm_2_uuid_2);
484 }
485