2 Copyright (C) 2013-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
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"
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>
53 #include <boost/test/unit_test.hpp>
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;
65 /** Test creation of a 2D SMPTE DCP from very simple inputs */
66 BOOST_AUTO_TEST_CASE (dcp_test1)
70 auto dcp = make_simple("build/test/DCP/dcp_test1");
71 dcp->set_issuer("OpenDCP 0.0.25");
72 dcp->set_creator("OpenDCP 0.0.25");
73 dcp->set_issue_date("2012-07-17T04:45:18+00:00");
74 dcp->set_annotation_text("A Test DCP");
77 /* build/test/DCP/dcp_test1 is checked against test/ref/DCP/dcp_test1 by run/tests */
80 /** Test creation of a 3D DCP from very simple inputs */
81 BOOST_AUTO_TEST_CASE (dcp_test2)
85 /* Some known metadata */
86 dcp::MXFMetadata mxf_meta;
87 mxf_meta.company_name = "OpenDCP";
88 mxf_meta.product_name = "OpenDCP";
89 mxf_meta.product_version = "0.0.25";
91 /* We're making build/test/DCP/dcp_test2 */
92 boost::filesystem::remove_all ("build/test/DCP/dcp_test2");
93 boost::filesystem::create_directories ("build/test/DCP/dcp_test2");
94 dcp::DCP d ("build/test/DCP/dcp_test2");
95 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
96 cpl->set_content_version (
97 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")
99 cpl->set_issuer ("OpenDCP 0.0.25");
100 cpl->set_creator ("OpenDCP 0.0.25");
101 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
102 cpl->set_annotation_text ("A Test DCP");
104 auto mp = make_shared<dcp::StereoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
105 mp->set_metadata (mxf_meta);
106 auto picture_writer = mp->start_write ("build/test/DCP/dcp_test2/video.mxf", false);
107 dcp::ArrayData j2c ("test/data/flat_red.j2c");
108 for (int i = 0; i < 24; ++i) {
110 picture_writer->write (j2c.data (), j2c.size ());
112 picture_writer->write (j2c.data (), j2c.size ());
114 picture_writer->finalize ();
116 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::Standard::SMPTE);
117 ms->set_metadata (mxf_meta);
118 auto sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf");
122 auto sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
123 BOOST_CHECK (sndfile);
124 float buffer[4096*6];
126 channels[0] = buffer;
128 auto N = sf_readf_float (sndfile, buffer, 4096);
129 sound_writer->write (channels, N);
135 sound_writer->finalize ();
137 cpl->add (make_shared<dcp::Reel>(
138 make_shared<dcp::ReelStereoPictureAsset>(mp, 0),
139 make_shared<dcp::ReelSoundAsset>(ms, 0)
144 d.set_issuer("OpenDCP 0.0.25");
145 d.set_creator("OpenDCP 0.0.25");
146 d.set_issue_date("2012-07-17T04:45:18+00:00");
147 d.set_annotation_text("Created by libdcp");
150 /* build/test/DCP/dcp_test2 is checked against test/ref/DCP/dcp_test2 by run/tests */
154 note (dcp::NoteType, string)
159 /** Test comparison of a DCP with itself */
160 BOOST_AUTO_TEST_CASE (dcp_test3)
162 dcp::DCP A ("test/ref/DCP/dcp_test1");
164 dcp::DCP B ("test/ref/DCP/dcp_test1");
167 BOOST_CHECK (A.equals (B, dcp::EqualityOptions(), boost::bind (¬e, _1, _2)));
170 /** Test comparison of a DCP with a different DCP */
171 BOOST_AUTO_TEST_CASE (dcp_test4)
173 dcp::DCP A ("test/ref/DCP/dcp_test1");
175 dcp::DCP B ("test/ref/DCP/dcp_test2");
178 BOOST_CHECK (!A.equals (B, dcp::EqualityOptions(), boost::bind (¬e, _1, _2)));
183 test_rewriting_sound(string name, bool modify)
185 using namespace boost::filesystem;
187 dcp::DCP A ("test/ref/DCP/dcp_test1");
190 BOOST_REQUIRE (!A.cpls().empty());
191 BOOST_REQUIRE (!A.cpls().front()->reels().empty());
192 auto A_picture = dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(A.cpls().front()->reels().front()->main_picture());
193 BOOST_REQUIRE (A_picture);
194 auto A_sound = dynamic_pointer_cast<dcp::ReelSoundAsset>(A.cpls().front()->reels().front()->main_sound());
196 string const picture = "j2c_5279f9aa-94d7-42a6-b0e0-e4eaec4e2a15.mxf";
198 remove_all ("build/test/" + name);
199 dcp::DCP B ("build/test/" + name);
200 auto reel = make_shared<dcp::Reel>();
202 BOOST_REQUIRE (A_picture->mono_asset());
203 BOOST_REQUIRE (A_picture->mono_asset()->file());
204 copy_file (A_picture->mono_asset()->file().get(), path("build") / "test" / name / picture);
205 reel->add(make_shared<dcp::ReelMonoPictureAsset>(make_shared<dcp::MonoPictureAsset>(path("build") / "test" / name / picture), 0));
207 auto reader = A_sound->asset()->start_read();
208 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);
209 auto writer = sound->start_write(path("build") / "test" / name / "pcm_8246f87f-e1df-4c42-a290-f3b3069ff021.mxf", {});
211 bool need_to_modify = modify;
212 for (int i = 0; i < A_sound->asset()->intrinsic_duration(); ++i) {
213 auto sf = reader->get_frame (i);
214 float* out[sf->channels()];
215 for (int j = 0; j < sf->channels(); ++j) {
216 out[j] = new float[sf->samples()];
218 for (int j = 0; j < sf->samples(); ++j) {
219 for (int k = 0; k < sf->channels(); ++k) {
220 out[k][j] = static_cast<float>(sf->get(k, j)) / (1 << 23);
221 if (need_to_modify) {
222 out[k][j] += 1.0 / (1 << 23);
223 need_to_modify = false;
227 writer->write (out, sf->samples());
228 for (int j = 0; j < sf->channels(); ++j) {
234 reel->add(make_shared<dcp::ReelSoundAsset>(sound, 0));
235 reel->add(simple_markers());
237 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
243 dcp::EqualityOptions eq;
244 eq.reel_hashes_can_differ = true;
245 eq.max_audio_sample_error = 0;
247 BOOST_CHECK (!A.equals(B, eq, boost::bind(¬e, _1, _2)));
249 BOOST_CHECK (A.equals(B, eq, boost::bind(¬e, _1, _2)));
253 /** Test comparison of a DCP with another that has the same picture and the same (but re-written) sound */
254 BOOST_AUTO_TEST_CASE (dcp_test9)
256 test_rewriting_sound ("dcp_test9", false);
259 /** Test comparison of a DCP with another that has the same picture and very slightly modified sound */
260 BOOST_AUTO_TEST_CASE (dcp_test10)
262 test_rewriting_sound ("dcp_test10", true);
265 /** Test creation of a 2D DCP with an Atmos track */
266 BOOST_AUTO_TEST_CASE (dcp_test5)
270 /* Some known metadata */
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";
276 /* We're making build/test/DCP/dcp_test5 */
277 boost::filesystem::remove_all ("build/test/DCP/dcp_test5");
278 boost::filesystem::create_directories ("build/test/DCP/dcp_test5");
279 dcp::DCP d ("build/test/DCP/dcp_test5");
280 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
281 cpl->set_content_version (
282 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")
284 cpl->set_issuer ("OpenDCP 0.0.25");
285 cpl->set_creator ("OpenDCP 0.0.25");
286 cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
287 cpl->set_annotation_text ("A Test DCP");
289 auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
290 mp->set_metadata (mxf_meta);
291 shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write ("build/test/DCP/dcp_test5/video.mxf", false);
292 dcp::ArrayData j2c ("test/data/flat_red.j2c");
293 for (int i = 0; i < 24; ++i) {
294 picture_writer->write (j2c.data (), j2c.size ());
296 picture_writer->finalize ();
298 auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::Standard::SMPTE);
299 ms->set_metadata (mxf_meta);
300 auto sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf");
304 SNDFILE* sndfile = sf_open ("test/data/1s_24-bit_48k_silence.wav", SFM_READ, &info);
305 BOOST_CHECK (sndfile);
306 float buffer[4096*6];
308 channels[0] = buffer;
310 sf_count_t N = sf_readf_float (sndfile, buffer, 4096);
311 sound_writer->write (channels, N);
317 sound_writer->finalize ();
319 auto am = make_shared<dcp::AtmosAsset>(private_test / "20160218_NameOfFilm_FTR_OV_EN_A_dcs_r01.mxf");
321 cpl->add(make_shared<dcp::Reel>(
322 make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
323 make_shared<dcp::ReelSoundAsset>(ms, 0),
324 shared_ptr<dcp::ReelSubtitleAsset>(),
325 shared_ptr<dcp::ReelMarkersAsset>(),
326 make_shared<dcp::ReelAtmosAsset>(am, 0)
331 d.set_issuer("OpenDCP 0.0.25");
332 d.set_creator("OpenDCP 0.0.25");
333 d.set_issue_date("2012-07-17T04:45:18+00:00");
334 d.set_annotation_text("Created by libdcp");
337 /* build/test/DCP/dcp_test5 is checked against test/ref/DCP/dcp_test5 by run/tests */
340 /** Basic tests of reading a 2D DCP with an Atmos track */
341 BOOST_AUTO_TEST_CASE (dcp_test6)
343 dcp::DCP dcp ("test/ref/DCP/dcp_test5");
346 BOOST_REQUIRE_EQUAL (dcp.cpls().size(), 1U);
347 BOOST_REQUIRE_EQUAL (dcp.cpls()[0]->reels().size(), 1U);
348 BOOST_CHECK (dcp.cpls().front()->reels().front()->main_picture());
349 BOOST_CHECK (dcp.cpls().front()->reels().front()->main_sound());
350 BOOST_CHECK (!dcp.cpls().front()->reels().front()->main_subtitle());
351 BOOST_CHECK (dcp.cpls().front()->reels().front()->atmos());
354 /** Test creation of a 2D Interop DCP from very simple inputs */
355 BOOST_AUTO_TEST_CASE (dcp_test7)
359 auto dcp = make_simple("build/test/DCP/dcp_test7", 1, 24, dcp::Standard::INTEROP);
360 dcp->set_issuer("OpenDCP 0.0.25");
361 dcp->set_creator("OpenDCP 0.0.25");
362 dcp->set_issue_date("2012-07-17T04:45:18+00:00");
363 dcp->set_annotation_text("Created by libdcp");
366 /* build/test/DCP/dcp_test7 is checked against test/ref/DCP/dcp_test7 by run/tests */
369 /** Test reading of a DCP with multiple CPLs */
370 BOOST_AUTO_TEST_CASE (dcp_test8)
372 dcp::DCP dcp (private_test / "data/SMPTE_TST-B1PB2P_S_EN-EN-CCAP_5171-HI-VI_2K_ISDCF_20151123_DPPT_SMPTE_combo/");
375 BOOST_REQUIRE_EQUAL (dcp.cpls().size(), 2U);
379 /** Test reading a DCP whose ASSETMAP contains assets not used by any PKL */
380 BOOST_AUTO_TEST_CASE (dcp_things_in_assetmap_not_in_pkl)
382 dcp::DCP dcp ("test/data/extra_assetmap");
383 BOOST_CHECK_NO_THROW (dcp.read());
387 /** Test that writing the XML for a DCP with no CPLs throws */
388 BOOST_AUTO_TEST_CASE (dcp_with_no_cpls)
390 dcp::DCP dcp ("build/test/dcp_with_no_cpls");
391 BOOST_REQUIRE_THROW (dcp.write_xml(), dcp::MiscError);
395 /** Test that writing the XML for a DCP with Interop CPLs makes a SMPTE assetmap */
396 BOOST_AUTO_TEST_CASE (dcp_with_interop_cpls)
398 boost::filesystem::path path = "build/test/dcp_with_interop_cpls";
399 boost::filesystem::remove_all (path);
401 auto cpl1 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP);
402 cpl1->add(make_shared<dcp::Reel>());
404 auto cpl2 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP);
405 cpl2->add(make_shared<dcp::Reel>());
408 BOOST_REQUIRE (boost::filesystem::exists(path / "ASSETMAP"));
409 BOOST_REQUIRE (!boost::filesystem::exists(path / "ASSETMAP.xml"));
413 /** Test that writing the XML for a DCP with SMPTE CPLs makes a SMPTE assetmap */
414 BOOST_AUTO_TEST_CASE (dcp_with_smpte_cpls)
416 boost::filesystem::path path = "build/test/dcp_with_smpte_cpls";
417 boost::filesystem::remove_all (path);
419 auto cpl1 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
420 cpl1->add(make_shared<dcp::Reel>());
422 auto cpl2 = make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
423 cpl2->add(make_shared<dcp::Reel>());
426 BOOST_REQUIRE (!boost::filesystem::exists(path / "ASSETMAP"));
427 BOOST_REQUIRE (boost::filesystem::exists(path / "ASSETMAP.xml"));
431 /** Test that writing the XML for a DCP with mixed-standard CPLs throws */
432 BOOST_AUTO_TEST_CASE (dcp_with_mixed_cpls)
434 dcp::DCP dcp ("build/test/dcp_with_mixed_cpls");
435 dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE));
436 dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::INTEROP));
437 dcp.add(make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE));
438 BOOST_REQUIRE_THROW (dcp.write_xml(), dcp::MiscError);
442 BOOST_AUTO_TEST_CASE (dcp_add_kdm_test)
444 /* Some CPLs, each with a reel */
446 shared_ptr<dcp::CPL> cpls[] = {
447 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE),
448 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE),
449 make_shared<dcp::CPL>("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE)
452 shared_ptr<dcp::Reel> reels[] = {
453 make_shared<dcp::Reel>(),
454 make_shared<dcp::Reel>(),
455 make_shared<dcp::Reel>()
458 for (auto i = 0; i < 3; ++i) {
459 cpls[i]->add(reels[i]);
462 dcp::DCP dcp ("build/test/dcp_add_kdm_test");
467 /* Simple KDM with one key that should be given to cpls[0] */
469 auto kdm_1 = dcp::DecryptedKDM({}, {}, "", "", "");
470 auto kdm_1_uuid = dcp::make_uuid();
471 kdm_1.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_1_uuid, dcp::Key(), cpls[0]->id(), dcp::Standard::SMPTE));
473 BOOST_REQUIRE_EQUAL (reels[0]->_kdms.size(), 1U);
474 BOOST_CHECK_EQUAL (reels[0]->_kdms[0].keys().size(), 1U);
475 BOOST_CHECK_EQUAL (reels[0]->_kdms[0].keys()[0].id(), kdm_1_uuid);
476 BOOST_CHECK_EQUAL (reels[1]->_kdms.size(), 0U);
477 BOOST_CHECK_EQUAL (reels[2]->_kdms.size(), 0U);
479 /* KDM with two keys that should be given to cpls[1] and cpls[2] */
481 auto kdm_2 = dcp::DecryptedKDM({}, {}, "", "", "");
482 auto kdm_2_uuid_1 = dcp::make_uuid();
483 kdm_2.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_2_uuid_1, dcp::Key(), cpls[1]->id(), dcp::Standard::SMPTE));
484 auto kdm_2_uuid_2 = dcp::make_uuid();
485 kdm_2.add_key (dcp::DecryptedKDMKey(string("MDIK"), kdm_2_uuid_2, dcp::Key(), cpls[2]->id(), dcp::Standard::SMPTE));
487 /* Unchanged from previous test */
488 BOOST_CHECK (reels[0]->_kdms.size() == 1);
489 /* kdm_2 should have been added to both the other CPLs */
490 BOOST_REQUIRE_EQUAL (reels[1]->_kdms.size(), 1U);
491 BOOST_REQUIRE_EQUAL (reels[1]->_kdms[0].keys().size(), 2U);
492 BOOST_CHECK_EQUAL (reels[1]->_kdms[0].keys()[0].id(), kdm_2_uuid_1);
493 BOOST_CHECK_EQUAL (reels[1]->_kdms[0].keys()[1].id(), kdm_2_uuid_2);
494 BOOST_REQUIRE_EQUAL (reels[2]->_kdms.size(), 1U);
495 BOOST_REQUIRE_EQUAL (reels[2]->_kdms[0].keys().size(), 2U);
496 BOOST_CHECK_EQUAL (reels[2]->_kdms[0].keys()[0].id(), kdm_2_uuid_1);
497 BOOST_CHECK_EQUAL (reels[2]->_kdms[0].keys()[1].id(), kdm_2_uuid_2);