Tolerate missing FullContentTitleText (DoM bug #2295).
[libdcp.git] / test / cpl_metadata_test.cc
1 /*
2     Copyright (C) 2020-2021 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
35 #include "certificate_chain.h"
36 #include "cpl.h"
37 #include "exceptions.h"
38 #include "language_tag.h"
39 #include "reel.h"
40 #include "reel_smpte_subtitle_asset.h"
41 #include "stream_operators.h"
42 #include "test.h"
43 #include <memory>
44 #include <boost/test/unit_test.hpp>
45
46
47 using std::list;
48 using std::make_shared;
49 using std::shared_ptr;
50 using std::string;
51 using std::vector;
52
53
54 BOOST_AUTO_TEST_CASE (cpl_metadata_bad_values_test)
55 {
56         dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
57         BOOST_CHECK_THROW (cpl.set_version_number(-1), dcp::BadSettingError);
58
59         vector<dcp::ContentVersion> cv = {
60                 dcp::ContentVersion("same-id", "version 1"),
61                 dcp::ContentVersion("same-id", "version 2")
62         };
63         BOOST_CHECK_THROW (cpl.set_content_versions(cv), dcp::DuplicateIdError);
64 }
65
66
67 BOOST_AUTO_TEST_CASE (main_sound_configuration_test1)
68 {
69         dcp::MainSoundConfiguration msc("51/L,R,C,LFE,-,-");
70         BOOST_CHECK_EQUAL (msc.to_string(), "51/L,R,C,LFE,-,-");
71         BOOST_CHECK_EQUAL (msc.channels(), 6);
72         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::FIVE_POINT_ONE);
73         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
74         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
75         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
76         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
77         BOOST_CHECK (!msc.mapping(4));
78         BOOST_CHECK (!msc.mapping(5));
79 }
80
81
82 BOOST_AUTO_TEST_CASE (main_sound_configuration_test2)
83 {
84         dcp::MainSoundConfiguration msc("71/L,R,C,LFE,-,-");
85         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,R,C,LFE,-,-");
86         BOOST_CHECK_EQUAL (msc.channels(), 6);
87         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
88         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
89         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
90         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
91         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
92         BOOST_CHECK (!msc.mapping(4));
93         BOOST_CHECK (!msc.mapping(5));
94 }
95
96
97 BOOST_AUTO_TEST_CASE (main_sound_configuration_test3)
98 {
99         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss");
100         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss");
101         BOOST_CHECK_EQUAL (msc.channels(), 6);
102         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
103         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
104         BOOST_CHECK (!msc.mapping(1));
105         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
106         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
107         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
108         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
109 }
110
111
112 BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
113 {
114         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
115         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
116         BOOST_CHECK_EQUAL (msc.channels(), 15);
117         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
118         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
119         BOOST_CHECK (!msc.mapping(1));
120         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
121         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
122         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
123         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
124         for (int i = 6; i < 15; ++i) {
125                 BOOST_CHECK (!msc.mapping(i));
126         }
127 }
128
129
130 BOOST_AUTO_TEST_CASE (main_sound_configuration_test5)
131 {
132         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
133         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
134         BOOST_CHECK_EQUAL (msc.channels(), 15);
135         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
136         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
137         BOOST_CHECK (!msc.mapping(1));
138         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
139         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
140         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
141         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
142         BOOST_CHECK_EQUAL (msc.mapping(6).get(), dcp::Channel::HI);
143         BOOST_CHECK_EQUAL (msc.mapping(7).get(), dcp::Channel::VI);
144         BOOST_CHECK (!msc.mapping(8));
145         BOOST_CHECK (!msc.mapping(9));
146         BOOST_CHECK_EQUAL (msc.mapping(10).get(), dcp::Channel::BSL);
147         BOOST_CHECK_EQUAL (msc.mapping(11).get(), dcp::Channel::BSR);
148         BOOST_CHECK_EQUAL (msc.mapping(12).get(), dcp::Channel::MOTION_DATA);
149         BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
150         BOOST_CHECK_EQUAL (msc.mapping(14).get(), dcp::Channel::SIGN_LANGUAGE);
151 }
152
153
154 BOOST_AUTO_TEST_CASE (luminance_test1)
155 {
156         BOOST_CHECK_NO_THROW (dcp::Luminance(4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE));
157         BOOST_CHECK_THROW (dcp::Luminance(-4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE), dcp::MiscError);
158 }
159
160
161 BOOST_AUTO_TEST_CASE (luminance_test2)
162 {
163         auto doc = make_shared<cxml::Document>("Luminance");
164
165         doc->read_string (
166                 "<Luminance units=\"candela-per-square-metre\">4.5</Luminance>"
167                 );
168
169         dcp::Luminance lum (doc);
170         BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
171         BOOST_CHECK_CLOSE (lum.value(), 4.5, 0.1);
172 }
173
174
175 BOOST_AUTO_TEST_CASE (luminance_test3)
176 {
177         auto doc = make_shared<cxml::Document>("Luminance");
178
179         doc->read_string (
180                 "<Luminance units=\"candela-per-square-motre\">4.5</Luminance>"
181                 );
182
183         BOOST_CHECK_THROW (new dcp::Luminance(doc), dcp::XMLError);
184 }
185
186
187 BOOST_AUTO_TEST_CASE (luminance_test4)
188 {
189         auto doc = make_shared<cxml::Document>("Luminance");
190
191         doc->read_string (
192                 "<Luminance units=\"candela-per-square-metre\">-4.5</Luminance>"
193                 );
194
195         /* We tolerate out-of-range values when reading from XML */
196         dcp::Luminance lum (doc);
197         BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
198         BOOST_CHECK_CLOSE (lum.value(), -4.5, 0.1);
199 }
200
201
202 /** A test where most CPL metadata is present */
203 BOOST_AUTO_TEST_CASE (cpl_metadata_read_test1)
204 {
205         dcp::CPL cpl("test/ref/cpl_metadata_test1.xml");
206
207         BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "full-content-title");
208         BOOST_CHECK (cpl.full_content_title_text_language().get() == "de");
209         BOOST_CHECK (cpl.release_territory().get() == "ES");
210         BOOST_CHECK_EQUAL (cpl.version_number().get(), 2);
211         BOOST_CHECK_EQUAL (cpl.status().get(), dcp::Status::FINAL);
212         BOOST_CHECK_EQUAL (cpl.chain().get(), "the-chain");
213         BOOST_CHECK_EQUAL (cpl.distributor().get(), "the-distributor");
214         BOOST_CHECK_EQUAL (cpl.facility().get(), "the-facility");
215         BOOST_CHECK (cpl.luminance() == dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
216
217         dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
218         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
219         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
220         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
221         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
222         BOOST_CHECK (!msc.mapping(4));
223         BOOST_CHECK (!msc.mapping(5));
224         BOOST_CHECK (!msc.mapping(6));
225         BOOST_CHECK (!msc.mapping(7));
226         BOOST_CHECK (!msc.mapping(8));
227         BOOST_CHECK (!msc.mapping(9));
228         BOOST_CHECK (!msc.mapping(10));
229         BOOST_CHECK (!msc.mapping(11));
230         BOOST_CHECK (!msc.mapping(12));
231         BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
232
233         BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
234         BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
235         BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
236
237         auto reels = cpl.reels ();
238         BOOST_REQUIRE_EQUAL (reels.size(), 1U);
239         BOOST_REQUIRE (reels.front()->main_subtitle()->language());
240         BOOST_CHECK_EQUAL (reels.front()->main_subtitle()->language().get(), "de-DE");
241
242         auto asl = cpl.additional_subtitle_languages();
243         BOOST_REQUIRE_EQUAL (asl.size(), 2U);
244         BOOST_CHECK_EQUAL (asl[0], "en-US");
245         BOOST_CHECK_EQUAL (asl[1], "fr-ZA");
246
247         BOOST_CHECK (cpl.additional_subtitle_languages() == asl);
248 }
249
250
251 /** A test where most CPL metadata is present */
252 BOOST_AUTO_TEST_CASE (cpl_metadata_write_test1)
253 {
254         RNGFixer fix;
255
256         dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
257         cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
258
259         vector<dcp::ContentVersion> cv = {
260                 dcp::ContentVersion("some-id", "version 1"),
261                 dcp::ContentVersion("another-id", "version 2")
262         };;
263         cpl.set_content_versions (cv);
264
265         cpl.set_full_content_title_text ("full-content-title");
266         cpl.set_full_content_title_text_language (dcp::LanguageTag("de"));
267         cpl.set_release_territory (dcp::LanguageTag::RegionSubtag("ES"));
268         cpl.set_version_number (2);
269         cpl.set_status (dcp::Status::FINAL);
270         cpl.set_chain ("the-chain");
271         cpl.set_distributor ("the-distributor");
272         cpl.set_facility ("the-facility");
273         cpl.set_luminance (dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
274         cpl.set_issuer ("libdcp1.6.4devel");
275         cpl.set_creator ("libdcp1.6.4devel");
276
277         dcp::MainSoundConfiguration msc(dcp::MCASoundField::SEVEN_POINT_ONE, 16);
278         msc.set_mapping (0, dcp::Channel::LEFT);
279         msc.set_mapping (1, dcp::Channel::RIGHT);
280         msc.set_mapping (2, dcp::Channel::CENTRE);
281         msc.set_mapping (3, dcp::Channel::LFE);
282         msc.set_mapping (13, dcp::Channel::SYNC_SIGNAL);
283         cpl.set_main_sound_configuration (msc.to_string());
284
285         cpl.set_main_sound_sample_rate (48000);
286         cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
287         cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
288
289         auto doc = make_shared<cxml::Document>("MainSubtitle");
290
291         doc->read_string (
292                 "<MainSubtitle>"
293                 "<Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>"
294                 "<AnnotationText>Goodbye world!</AnnotationText>"
295                 "<EditRate>25 1</EditRate>"
296                 "<IntrinsicDuration>1870</IntrinsicDuration>"
297                 "<EntryPoint>0</EntryPoint>"
298                 "<Duration>525</Duration>"
299                 "<KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>"
300                 "<Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>"
301                 "<Language>de-DE</Language>"
302                 "</MainSubtitle>"
303                 );
304
305         auto reel = make_shared<dcp::Reel>();
306         reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
307         reel->add (make_shared<dcp::ReelSMPTESubtitleAsset>(doc));
308         cpl.add (reel);
309
310         auto lt = { dcp::LanguageTag("en-US"), dcp::LanguageTag("fr-ZA") };
311         cpl.set_additional_subtitle_languages (lt);
312
313         cpl.set_sign_language_video_language (dcp::LanguageTag("bzs"));
314
315         cpl.write_xml ("build/test/cpl_metadata_write_test1.xml", {});
316         check_xml (
317                 dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
318                 dcp::file_to_string("build/test/cpl_metadata_write_test1.xml"),
319                 {"Id", "Hash"}
320                 );
321 }
322
323
324 /** A test where most CPL metadata is present */
325 BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_1)
326 {
327         dcp::CPL cpl ("test/ref/cpl_metadata_test1.xml");
328         cpl.write_xml ("build/test/cpl_metadata_roundtrip_test1.xml", shared_ptr<dcp::CertificateChain>());
329         check_xml (
330                 dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
331                 dcp::file_to_string("build/test/cpl_metadata_roundtrip_test1.xml"),
332                 {"Id"}
333                 );
334 }
335
336
337 /** A test where only a bare minimum of CPL metadata is present */
338 BOOST_AUTO_TEST_CASE (cpl_metadata_write_test2)
339 {
340         RNGFixer fix;
341
342         dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
343         cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
344         cpl.set_content_version (dcp::ContentVersion("id", "version"));
345         cpl.set_issuer ("libdcp1.6.4devel");
346         cpl.set_creator ("libdcp1.6.4devel");
347
348         dcp::MainSoundConfiguration msc(dcp::MCASoundField::SEVEN_POINT_ONE, 16);
349         msc.set_mapping (0, dcp::Channel::LEFT);
350         msc.set_mapping (1, dcp::Channel::RIGHT);
351         msc.set_mapping (2, dcp::Channel::CENTRE);
352         msc.set_mapping (3, dcp::Channel::LFE);
353         msc.set_mapping (13, dcp::Channel::SYNC_SIGNAL);
354         cpl.set_main_sound_configuration (msc.to_string());
355
356         cpl.set_main_sound_sample_rate (48000);
357         cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
358         cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
359
360         auto reel = make_shared<dcp::Reel>();
361         reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
362         cpl.add (reel);
363
364         cpl.write_xml ("build/test/cpl_metadata_write_test2.xml", {});
365         check_xml (
366                 dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
367                 dcp::file_to_string("build/test/cpl_metadata_write_test2.xml"),
368                 {"Id", "Hash"}
369                 );
370 }
371
372
373 /** A test where only a bare minimum of CPL metadata is present */
374 BOOST_AUTO_TEST_CASE (cpl_metadata_read_test2)
375 {
376         dcp::CPL cpl("test/ref/cpl_metadata_test2.xml");
377
378         BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "");
379         BOOST_CHECK (!cpl.full_content_title_text_language());
380         BOOST_CHECK (!cpl.release_territory());
381         BOOST_CHECK (!cpl.version_number());
382         BOOST_CHECK (!cpl.status());
383         BOOST_CHECK (!cpl.chain());
384         BOOST_CHECK (!cpl.distributor());
385         BOOST_CHECK (!cpl.facility());
386         BOOST_CHECK (!cpl.luminance());
387
388         dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
389         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
390         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
391         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
392         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
393         BOOST_CHECK (!msc.mapping(4));
394         BOOST_CHECK (!msc.mapping(5));
395         BOOST_CHECK (!msc.mapping(6));
396         BOOST_CHECK (!msc.mapping(7));
397         BOOST_CHECK (!msc.mapping(8));
398         BOOST_CHECK (!msc.mapping(9));
399         BOOST_CHECK (!msc.mapping(10));
400         BOOST_CHECK (!msc.mapping(11));
401         BOOST_CHECK (!msc.mapping(12));
402         BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
403
404         BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
405         BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
406         BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
407
408         auto reels = cpl.reels ();
409         BOOST_REQUIRE_EQUAL (reels.size(), 1U);
410 }
411
412
413 /** A test where only a bare minimum of CPL metadata is present */
414 BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_2)
415 {
416         dcp::CPL cpl ("test/ref/cpl_metadata_test2.xml");
417         cpl.write_xml ("build/test/cpl_metadata_roundtrip_test2.xml", shared_ptr<dcp::CertificateChain>());
418         check_xml (
419                 dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
420                 dcp::file_to_string("build/test/cpl_metadata_roundtrip_test2.xml"),
421                 {"Id"}
422                 );
423 }
424
425
426 BOOST_AUTO_TEST_CASE(check_that_missing_full_content_title_text_is_tolerated)
427 {
428         dcp::CPL cpl("test/ref/cpl_metadata_test3.xml");
429 }
430