2 Copyright (C) 2020-2021 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.
35 #include "certificate_chain.h"
36 #include "compose.hpp"
38 #include "exceptions.h"
39 #include "language_tag.h"
41 #include "reel_mono_picture_asset.h"
42 #include "reel_smpte_subtitle_asset.h"
43 #include "reel_sound_asset.h"
44 #include "stream_operators.h"
47 #include <boost/test/unit_test.hpp>
51 using std::make_shared;
52 using std::shared_ptr;
57 BOOST_AUTO_TEST_CASE (cpl_metadata_bad_values_test)
59 dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
60 BOOST_CHECK_THROW (cpl.set_version_number(-1), dcp::BadSettingError);
62 vector<dcp::ContentVersion> cv = {
63 dcp::ContentVersion("same-id", "version 1"),
64 dcp::ContentVersion("same-id", "version 2")
66 BOOST_CHECK_THROW (cpl.set_content_versions(cv), dcp::DuplicateIdError);
70 BOOST_AUTO_TEST_CASE (main_sound_configuration_test1)
72 dcp::MainSoundConfiguration msc("51/L,R,C,LFE,-,-");
73 BOOST_CHECK_EQUAL (msc.to_string(), "51/L,R,C,LFE,-,-");
74 BOOST_CHECK_EQUAL (msc.channels(), 6);
75 BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::FIVE_POINT_ONE);
76 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
77 BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
78 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
79 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
80 BOOST_CHECK (!msc.mapping(4));
81 BOOST_CHECK (!msc.mapping(5));
85 BOOST_AUTO_TEST_CASE (main_sound_configuration_test2)
87 dcp::MainSoundConfiguration msc("71/L,R,C,LFE,-,-");
88 BOOST_CHECK_EQUAL (msc.to_string(), "71/L,R,C,LFE,-,-");
89 BOOST_CHECK_EQUAL (msc.channels(), 6);
90 BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
91 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
92 BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
93 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
94 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
95 BOOST_CHECK (!msc.mapping(4));
96 BOOST_CHECK (!msc.mapping(5));
100 BOOST_AUTO_TEST_CASE (main_sound_configuration_test3)
102 dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss");
103 BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss");
104 BOOST_CHECK_EQUAL (msc.channels(), 6);
105 BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
106 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
107 BOOST_CHECK (!msc.mapping(1));
108 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
109 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
110 BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
111 BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
115 BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
117 dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
118 BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
119 BOOST_CHECK_EQUAL (msc.channels(), 15);
120 BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
121 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
122 BOOST_CHECK (!msc.mapping(1));
123 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
124 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
125 BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
126 BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
127 for (int i = 6; i < 15; ++i) {
128 BOOST_CHECK (!msc.mapping(i));
133 BOOST_AUTO_TEST_CASE (main_sound_configuration_test5)
135 dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
136 BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
137 BOOST_CHECK_EQUAL (msc.channels(), 15);
138 BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
139 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
140 BOOST_CHECK (!msc.mapping(1));
141 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
142 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
143 BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
144 BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
145 BOOST_CHECK_EQUAL (msc.mapping(6).get(), dcp::Channel::HI);
146 BOOST_CHECK_EQUAL (msc.mapping(7).get(), dcp::Channel::VI);
147 BOOST_CHECK (!msc.mapping(8));
148 BOOST_CHECK (!msc.mapping(9));
149 BOOST_CHECK_EQUAL (msc.mapping(10).get(), dcp::Channel::BSL);
150 BOOST_CHECK_EQUAL (msc.mapping(11).get(), dcp::Channel::BSR);
151 BOOST_CHECK_EQUAL (msc.mapping(12).get(), dcp::Channel::MOTION_DATA);
152 BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
153 BOOST_CHECK_EQUAL (msc.mapping(14).get(), dcp::Channel::SIGN_LANGUAGE);
157 BOOST_AUTO_TEST_CASE (luminance_test1)
159 BOOST_CHECK_NO_THROW (dcp::Luminance(4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE));
160 BOOST_CHECK_THROW (dcp::Luminance(-4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE), dcp::MiscError);
164 BOOST_AUTO_TEST_CASE (luminance_test2)
166 auto doc = make_shared<cxml::Document>("Luminance");
169 "<Luminance units=\"candela-per-square-metre\">4.5</Luminance>"
172 dcp::Luminance lum (doc);
173 BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
174 BOOST_CHECK_CLOSE (lum.value(), 4.5, 0.1);
178 BOOST_AUTO_TEST_CASE (luminance_test3)
180 auto doc = make_shared<cxml::Document>("Luminance");
183 "<Luminance units=\"candela-per-square-motre\">4.5</Luminance>"
186 BOOST_CHECK_THROW (new dcp::Luminance(doc), dcp::XMLError);
190 BOOST_AUTO_TEST_CASE (luminance_test4)
192 auto doc = make_shared<cxml::Document>("Luminance");
195 "<Luminance units=\"candela-per-square-metre\">-4.5</Luminance>"
198 /* We tolerate out-of-range values when reading from XML */
199 dcp::Luminance lum (doc);
200 BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
201 BOOST_CHECK_CLOSE (lum.value(), -4.5, 0.1);
205 /** A test where most CPL metadata is present */
206 BOOST_AUTO_TEST_CASE (cpl_metadata_read_test1)
208 dcp::CPL cpl("test/ref/cpl_metadata_test1.xml");
210 BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "full-content-title");
211 BOOST_CHECK (cpl.full_content_title_text_language().get() == "de");
212 BOOST_CHECK (cpl.release_territory().get() == "ES");
213 BOOST_CHECK_EQUAL (cpl.version_number().get(), 2);
214 BOOST_CHECK_EQUAL (cpl.status().get(), dcp::Status::FINAL);
215 BOOST_CHECK_EQUAL (cpl.chain().get(), "the-chain");
216 BOOST_CHECK_EQUAL (cpl.distributor().get(), "the-distributor");
217 BOOST_CHECK_EQUAL (cpl.facility().get(), "the-facility");
218 BOOST_CHECK (cpl.luminance() == dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
220 dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
221 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
222 BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
223 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
224 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
225 BOOST_CHECK (!msc.mapping(4));
226 BOOST_CHECK (!msc.mapping(5));
227 BOOST_CHECK (!msc.mapping(6));
228 BOOST_CHECK (!msc.mapping(7));
229 BOOST_CHECK (!msc.mapping(8));
230 BOOST_CHECK (!msc.mapping(9));
231 BOOST_CHECK (!msc.mapping(10));
232 BOOST_CHECK (!msc.mapping(11));
233 BOOST_CHECK (!msc.mapping(12));
234 BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
236 BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
237 BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
238 BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
240 auto reels = cpl.reels ();
241 BOOST_REQUIRE_EQUAL (reels.size(), 1U);
242 BOOST_REQUIRE (reels.front()->main_subtitle()->language());
243 BOOST_CHECK_EQUAL (reels.front()->main_subtitle()->language().get(), "de-DE");
245 auto asl = cpl.additional_subtitle_languages();
246 BOOST_REQUIRE_EQUAL (asl.size(), 2U);
247 BOOST_CHECK_EQUAL (asl[0], "en-US");
248 BOOST_CHECK_EQUAL (asl[1], "fr-ZA");
250 BOOST_CHECK (cpl.additional_subtitle_languages() == asl);
254 /** A test where most CPL metadata is present */
255 BOOST_AUTO_TEST_CASE (cpl_metadata_write_test1)
259 dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
260 cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
262 vector<dcp::ContentVersion> cv = {
263 dcp::ContentVersion("some-id", "version 1"),
264 dcp::ContentVersion("another-id", "version 2")
266 cpl.set_content_versions (cv);
268 cpl.set_full_content_title_text ("full-content-title");
269 cpl.set_full_content_title_text_language (dcp::LanguageTag("de"));
270 cpl.set_release_territory (dcp::LanguageTag::RegionSubtag("ES"));
271 cpl.set_version_number (2);
272 cpl.set_status (dcp::Status::FINAL);
273 cpl.set_chain ("the-chain");
274 cpl.set_distributor ("the-distributor");
275 cpl.set_facility ("the-facility");
276 cpl.set_luminance (dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
277 cpl.set_issuer ("libdcp1.6.4devel");
278 cpl.set_creator ("libdcp1.6.4devel");
280 dcp::MainSoundConfiguration msc(dcp::MCASoundField::SEVEN_POINT_ONE, 16);
281 msc.set_mapping (0, dcp::Channel::LEFT);
282 msc.set_mapping (1, dcp::Channel::RIGHT);
283 msc.set_mapping (2, dcp::Channel::CENTRE);
284 msc.set_mapping (3, dcp::Channel::LFE);
285 msc.set_mapping (13, dcp::Channel::SYNC_SIGNAL);
286 cpl.set_main_sound_configuration (msc.to_string());
288 cpl.set_main_sound_sample_rate (48000);
289 cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
290 cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
292 auto doc = make_shared<cxml::Document>("MainSubtitle");
296 "<Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>"
297 "<AnnotationText>Goodbye world!</AnnotationText>"
298 "<EditRate>25 1</EditRate>"
299 "<IntrinsicDuration>1870</IntrinsicDuration>"
300 "<EntryPoint>0</EntryPoint>"
301 "<Duration>525</Duration>"
302 "<KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>"
303 "<Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>"
304 "<Language>de-DE</Language>"
308 auto reel = make_shared<dcp::Reel>();
309 reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
310 reel->add (make_shared<dcp::ReelSMPTESubtitleAsset>(doc));
313 auto lt = { dcp::LanguageTag("en-US"), dcp::LanguageTag("fr-ZA") };
314 cpl.set_additional_subtitle_languages (lt);
316 cpl.set_sign_language_video_language (dcp::LanguageTag("bzs"));
318 cpl.write_xml ("build/test/cpl_metadata_write_test1.xml", {});
320 dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
321 dcp::file_to_string("build/test/cpl_metadata_write_test1.xml"),
327 /** A test where most CPL metadata is present */
328 BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_1)
330 dcp::CPL cpl ("test/ref/cpl_metadata_test1.xml");
331 cpl.write_xml ("build/test/cpl_metadata_roundtrip_test1.xml", shared_ptr<dcp::CertificateChain>());
333 dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
334 dcp::file_to_string("build/test/cpl_metadata_roundtrip_test1.xml"),
340 /** A test where only a bare minimum of CPL metadata is present */
341 BOOST_AUTO_TEST_CASE (cpl_metadata_write_test2)
345 dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
346 cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
347 cpl.set_content_version (dcp::ContentVersion("id", "version"));
348 cpl.set_issuer ("libdcp1.6.4devel");
349 cpl.set_creator ("libdcp1.6.4devel");
351 dcp::MainSoundConfiguration msc(dcp::MCASoundField::SEVEN_POINT_ONE, 16);
352 msc.set_mapping (0, dcp::Channel::LEFT);
353 msc.set_mapping (1, dcp::Channel::RIGHT);
354 msc.set_mapping (2, dcp::Channel::CENTRE);
355 msc.set_mapping (3, dcp::Channel::LFE);
356 msc.set_mapping (13, dcp::Channel::SYNC_SIGNAL);
357 cpl.set_main_sound_configuration (msc.to_string());
359 cpl.set_main_sound_sample_rate (48000);
360 cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
361 cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
363 auto reel = make_shared<dcp::Reel>();
364 reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
367 cpl.write_xml ("build/test/cpl_metadata_write_test2.xml", {});
369 dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
370 dcp::file_to_string("build/test/cpl_metadata_write_test2.xml"),
376 /** A test where only a bare minimum of CPL metadata is present */
377 BOOST_AUTO_TEST_CASE (cpl_metadata_read_test2)
379 dcp::CPL cpl("test/ref/cpl_metadata_test2.xml");
381 BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "");
382 BOOST_CHECK (!cpl.full_content_title_text_language());
383 BOOST_CHECK (!cpl.release_territory());
384 BOOST_CHECK (!cpl.version_number());
385 BOOST_CHECK (!cpl.status());
386 BOOST_CHECK (!cpl.chain());
387 BOOST_CHECK (!cpl.distributor());
388 BOOST_CHECK (!cpl.facility());
389 BOOST_CHECK (!cpl.luminance());
391 dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
392 BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
393 BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
394 BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
395 BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
396 BOOST_CHECK (!msc.mapping(4));
397 BOOST_CHECK (!msc.mapping(5));
398 BOOST_CHECK (!msc.mapping(6));
399 BOOST_CHECK (!msc.mapping(7));
400 BOOST_CHECK (!msc.mapping(8));
401 BOOST_CHECK (!msc.mapping(9));
402 BOOST_CHECK (!msc.mapping(10));
403 BOOST_CHECK (!msc.mapping(11));
404 BOOST_CHECK (!msc.mapping(12));
405 BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
407 BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
408 BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
409 BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
411 auto reels = cpl.reels ();
412 BOOST_REQUIRE_EQUAL (reels.size(), 1U);
416 /** A test where only a bare minimum of CPL metadata is present */
417 BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_2)
419 dcp::CPL cpl ("test/ref/cpl_metadata_test2.xml");
420 cpl.write_xml ("build/test/cpl_metadata_roundtrip_test2.xml", shared_ptr<dcp::CertificateChain>());
422 dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
423 dcp::file_to_string("build/test/cpl_metadata_roundtrip_test2.xml"),
429 BOOST_AUTO_TEST_CASE(check_that_missing_full_content_title_text_is_tolerated)
431 dcp::CPL cpl("test/ref/cpl_metadata_test3.xml");
437 check_audio_channel_label_sub_descriptors(int channels)
439 boost::filesystem::path path = dcp::String::compose("build/test/check_audio_channel_label_sub_descriptors_%1", channels);
440 auto constexpr sample_rate = 48000;
442 boost::filesystem::remove_all(path);
443 boost::filesystem::create_directories(path);
444 auto dcp = make_shared<dcp::DCP>(path);
445 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
446 cpl->set_main_sound_configuration("wrong");
447 cpl->set_main_sound_sample_rate(48000);
448 cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
449 cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
450 cpl->set_version_number(1);
452 auto mp = simple_picture(path, "", 240);
453 auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", 240, sample_rate, boost::none, channels);
455 auto reel = make_shared<dcp::Reel>(
456 shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
457 shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
463 cpl->write_xml(path / "cpl.xml", {});
465 cxml::Document check("CompositionPlaylist");
466 check.read_file(path / "cpl.xml");
468 auto reel_list = check.node_child("ReelList");
469 BOOST_REQUIRE(reel_list);
470 auto check_reel = reel_list->node_child("Reel");
472 auto asset_list = check_reel->node_child("AssetList");
473 BOOST_REQUIRE(asset_list);
474 auto composition_metadata_asset = asset_list->node_child("CompositionMetadataAsset");
475 BOOST_REQUIRE(composition_metadata_asset);
476 auto mca_sub_descriptors = composition_metadata_asset->node_child("MCASubDescriptors");
477 BOOST_REQUIRE(mca_sub_descriptors);
478 auto channel_label_sub_descriptors = mca_sub_descriptors->node_children("AudioChannelLabelSubDescriptor");
480 BOOST_CHECK_EQUAL(channel_label_sub_descriptors.size(), channels);
482 for (auto sub: channel_label_sub_descriptors) {
483 BOOST_CHECK_EQUAL(sub->number_child<int>("MCAChannelID"), index);
489 BOOST_AUTO_TEST_CASE(include_the_right_number_of_channel_label_sub_descriptors)
491 check_audio_channel_label_sub_descriptors(2);
492 check_audio_channel_label_sub_descriptors(6);
493 check_audio_channel_label_sub_descriptors(8);