Specify CPL standard on construction.
[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         cv.push_back (dcp::ContentVersion("same-id", "version 1"));
61         cv.push_back (dcp::ContentVersion("same-id", "version 2"));
62         BOOST_CHECK_THROW (cpl.set_content_versions(cv), dcp::DuplicateIdError);
63 }
64
65
66 BOOST_AUTO_TEST_CASE (main_sound_configuration_test1)
67 {
68         dcp::MainSoundConfiguration msc("51/L,R,C,LFE,-,-");
69         BOOST_CHECK_EQUAL (msc.to_string(), "51/L,R,C,LFE,-,-");
70         BOOST_CHECK_EQUAL (msc.channels(), 6);
71         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::FIVE_POINT_ONE);
72         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
73         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
74         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
75         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
76         BOOST_CHECK (!msc.mapping(4));
77         BOOST_CHECK (!msc.mapping(5));
78 }
79
80
81 BOOST_AUTO_TEST_CASE (main_sound_configuration_test2)
82 {
83         dcp::MainSoundConfiguration msc("71/L,R,C,LFE,-,-");
84         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,R,C,LFE,-,-");
85         BOOST_CHECK_EQUAL (msc.channels(), 6);
86         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
87         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
88         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
89         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
90         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
91         BOOST_CHECK (!msc.mapping(4));
92         BOOST_CHECK (!msc.mapping(5));
93 }
94
95
96 BOOST_AUTO_TEST_CASE (main_sound_configuration_test3)
97 {
98         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss");
99         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss");
100         BOOST_CHECK_EQUAL (msc.channels(), 6);
101         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
102         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
103         BOOST_CHECK (!msc.mapping(1));
104         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
105         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
106         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
107         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
108 }
109
110
111 BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
112 {
113         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
114         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
115         BOOST_CHECK_EQUAL (msc.channels(), 15);
116         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
117         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
118         BOOST_CHECK (!msc.mapping(1));
119         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
120         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
121         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
122         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
123         for (int i = 6; i < 15; ++i) {
124                 BOOST_CHECK (!msc.mapping(i));
125         }
126 }
127
128
129 BOOST_AUTO_TEST_CASE (main_sound_configuration_test5)
130 {
131         dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
132         BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
133         BOOST_CHECK_EQUAL (msc.channels(), 15);
134         BOOST_CHECK_EQUAL (msc.field(), dcp::MCASoundField::SEVEN_POINT_ONE);
135         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
136         BOOST_CHECK (!msc.mapping(1));
137         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
138         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
139         BOOST_CHECK_EQUAL (msc.mapping(4).get(), dcp::Channel::LS);
140         BOOST_CHECK_EQUAL (msc.mapping(5).get(), dcp::Channel::RS);
141         BOOST_CHECK_EQUAL (msc.mapping(6).get(), dcp::Channel::HI);
142         BOOST_CHECK_EQUAL (msc.mapping(7).get(), dcp::Channel::VI);
143         BOOST_CHECK (!msc.mapping(8));
144         BOOST_CHECK (!msc.mapping(9));
145         BOOST_CHECK_EQUAL (msc.mapping(10).get(), dcp::Channel::BSL);
146         BOOST_CHECK_EQUAL (msc.mapping(11).get(), dcp::Channel::BSR);
147         BOOST_CHECK_EQUAL (msc.mapping(12).get(), dcp::Channel::MOTION_DATA);
148         BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
149         BOOST_CHECK_EQUAL (msc.mapping(14).get(), dcp::Channel::SIGN_LANGUAGE);
150 }
151
152
153 BOOST_AUTO_TEST_CASE (luminance_test1)
154 {
155         BOOST_CHECK_NO_THROW (dcp::Luminance(4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE));
156         BOOST_CHECK_THROW (dcp::Luminance(-4, dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE), dcp::MiscError);
157 }
158
159
160 BOOST_AUTO_TEST_CASE (luminance_test2)
161 {
162         shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
163
164         doc->read_string (
165                 "<Luminance units=\"candela-per-square-metre\">4.5</Luminance>"
166                 );
167
168         dcp::Luminance lum (doc);
169         BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
170         BOOST_CHECK_CLOSE (lum.value(), 4.5, 0.1);
171 }
172
173
174 BOOST_AUTO_TEST_CASE (luminance_test3)
175 {
176         shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
177
178         doc->read_string (
179                 "<Luminance units=\"candela-per-square-motre\">4.5</Luminance>"
180                 );
181
182         BOOST_CHECK_THROW (new dcp::Luminance(doc), dcp::XMLError);
183 }
184
185
186 BOOST_AUTO_TEST_CASE (luminance_test4)
187 {
188         shared_ptr<cxml::Document> doc (new cxml::Document("Luminance"));
189
190         doc->read_string (
191                 "<Luminance units=\"candela-per-square-metre\">-4.5</Luminance>"
192                 );
193
194         /* We tolerate out-of-range values when reading from XML */
195         dcp::Luminance lum (doc);
196         BOOST_CHECK (lum.unit() == dcp::Luminance::Unit::CANDELA_PER_SQUARE_METRE);
197         BOOST_CHECK_CLOSE (lum.value(), -4.5, 0.1);
198 }
199
200
201 /** A test where most CPL metadata is present */
202 BOOST_AUTO_TEST_CASE (cpl_metadata_read_test1)
203 {
204         dcp::CPL cpl("test/ref/cpl_metadata_test1.xml");
205
206         BOOST_CHECK_EQUAL (cpl.full_content_title_text().get(), "full-content-title");
207         BOOST_CHECK (cpl.full_content_title_text_language().get() == "de");
208         BOOST_CHECK (cpl.release_territory().get() == "ES");
209         BOOST_CHECK_EQUAL (cpl.version_number().get(), 2);
210         BOOST_CHECK_EQUAL (cpl.status().get(), dcp::Status::FINAL);
211         BOOST_CHECK_EQUAL (cpl.chain().get(), "the-chain");
212         BOOST_CHECK_EQUAL (cpl.distributor().get(), "the-distributor");
213         BOOST_CHECK_EQUAL (cpl.facility().get(), "the-facility");
214         BOOST_CHECK (cpl.luminance() == dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
215
216         dcp::MainSoundConfiguration msc(cpl.main_sound_configuration().get());
217         BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::Channel::LEFT);
218         BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::Channel::RIGHT);
219         BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::Channel::CENTRE);
220         BOOST_CHECK_EQUAL (msc.mapping(3).get(), dcp::Channel::LFE);
221         BOOST_CHECK (!msc.mapping(4));
222         BOOST_CHECK (!msc.mapping(5));
223         BOOST_CHECK (!msc.mapping(6));
224         BOOST_CHECK (!msc.mapping(7));
225         BOOST_CHECK (!msc.mapping(8));
226         BOOST_CHECK (!msc.mapping(9));
227         BOOST_CHECK (!msc.mapping(10));
228         BOOST_CHECK (!msc.mapping(11));
229         BOOST_CHECK (!msc.mapping(12));
230         BOOST_CHECK_EQUAL (msc.mapping(13).get(), dcp::Channel::SYNC_SIGNAL);
231
232         BOOST_CHECK_EQUAL (cpl.main_sound_sample_rate().get(), 48000);
233         BOOST_CHECK (cpl.main_picture_stored_area().get() == dcp::Size(1998, 1080));
234         BOOST_CHECK (cpl.main_picture_active_area().get() == dcp::Size(1440, 1080));
235
236         auto reels = cpl.reels ();
237         BOOST_REQUIRE_EQUAL (reels.size(), 1);
238         BOOST_REQUIRE (reels.front()->main_subtitle()->language());
239         BOOST_CHECK_EQUAL (reels.front()->main_subtitle()->language().get(), "de-DE");
240
241         vector<string> asl = cpl.additional_subtitle_languages();
242         BOOST_REQUIRE_EQUAL (asl.size(), 2);
243         BOOST_CHECK_EQUAL (asl[0], "en-US");
244         BOOST_CHECK_EQUAL (asl[1], "fr-ZA");
245
246         BOOST_CHECK (cpl.additional_subtitle_languages() == asl);
247 }
248
249
250 /** A test where most CPL metadata is present */
251 BOOST_AUTO_TEST_CASE (cpl_metadata_write_test1)
252 {
253         RNGFixer fix;
254
255         dcp::CPL cpl("", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
256         cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
257
258         vector<dcp::ContentVersion> cv;
259         cv.push_back (dcp::ContentVersion("some-id", "version 1"));
260         cv.push_back (dcp::ContentVersion("another-id", "version 2"));
261         cpl.set_content_versions (cv);
262
263         cpl.set_full_content_title_text ("full-content-title");
264         cpl.set_full_content_title_text_language (dcp::LanguageTag("de"));
265         cpl.set_release_territory (dcp::LanguageTag::RegionSubtag("ES"));
266         cpl.set_version_number (2);
267         cpl.set_status (dcp::Status::FINAL);
268         cpl.set_chain ("the-chain");
269         cpl.set_distributor ("the-distributor");
270         cpl.set_facility ("the-facility");
271         cpl.set_luminance (dcp::Luminance(4.5, dcp::Luminance::Unit::FOOT_LAMBERT));
272         cpl.set_issuer ("libdcp1.6.4devel");
273         cpl.set_creator ("libdcp1.6.4devel");
274
275         dcp::MainSoundConfiguration msc(dcp::MCASoundField::SEVEN_POINT_ONE, 16);
276         msc.set_mapping (0, dcp::Channel::LEFT);
277         msc.set_mapping (1, dcp::Channel::RIGHT);
278         msc.set_mapping (2, dcp::Channel::CENTRE);
279         msc.set_mapping (3, dcp::Channel::LFE);
280         msc.set_mapping (13, dcp::Channel::SYNC_SIGNAL);
281         cpl.set_main_sound_configuration (msc.to_string());
282
283         cpl.set_main_sound_sample_rate (48000);
284         cpl.set_main_picture_stored_area (dcp::Size(1998, 1080));
285         cpl.set_main_picture_active_area (dcp::Size(1440, 1080));
286
287         shared_ptr<cxml::Document> doc (new cxml::Document("MainSubtitle"));
288
289         doc->read_string (
290                 "<MainSubtitle>"
291                 "<Id>urn:uuid:8bca1489-aab1-9259-a4fd-8150abc1de12</Id>"
292                 "<AnnotationText>Goodbye world!</AnnotationText>"
293                 "<EditRate>25 1</EditRate>"
294                 "<IntrinsicDuration>1870</IntrinsicDuration>"
295                 "<EntryPoint>0</EntryPoint>"
296                 "<Duration>525</Duration>"
297                 "<KeyId>urn:uuid:540cbf10-ab14-0233-ab1f-fb31501cabfa</KeyId>"
298                 "<Hash>3EABjX9BB1CAWhLUtHhrGSyLgOY=</Hash>"
299                 "<Language>de-DE</Language>"
300                 "</MainSubtitle>"
301                 );
302
303         shared_ptr<dcp::Reel> reel(new dcp::Reel());
304         reel->add (black_picture_asset("build/test/cpl_metadata_write_test1"));
305         reel->add (make_shared<dcp::ReelSMPTESubtitleAsset>(doc));
306         cpl.add (reel);
307
308         vector<dcp::LanguageTag> lt;
309         lt.push_back(dcp::LanguageTag("en-US"));
310         lt.push_back(dcp::LanguageTag("fr-ZA"));
311         cpl.set_additional_subtitle_languages (lt);
312
313         cpl.write_xml ("build/test/cpl_metadata_write_test1.xml", shared_ptr<dcp::CertificateChain>());
314         check_xml (
315                 dcp::file_to_string("test/ref/cpl_metadata_test1.xml"),
316                 dcp::file_to_string("build/test/cpl_metadata_write_test1.xml"),
317                 vector<string>()
318                 );
319 }
320
321
322 /** A test where most CPL metadata is present */
323 BOOST_AUTO_TEST_CASE (cpl_metadata_roundtrip_test_1)
324 {
325         dcp::CPL cpl ("test/ref/cpl_metadata_test1.xml");
326         cpl.write_xml ("build/test/cpl_metadata_roundtrip_test1.xml", shared_ptr<dcp::CertificateChain>());
327         vector<string> ignore;
328         ignore.push_back ("Id");
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                 ignore
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         shared_ptr<dcp::Reel> reel(new 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", shared_ptr<dcp::CertificateChain>());
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                 vector<string>()
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(), 1);
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         vector<string> ignore;
418         ignore.push_back ("Id");
419         cpl.write_xml ("build/test/cpl_metadata_roundtrip_test2.xml", shared_ptr<dcp::CertificateChain>());
420         check_xml (
421                 dcp::file_to_string("test/ref/cpl_metadata_test2.xml"),
422                 dcp::file_to_string("build/test/cpl_metadata_roundtrip_test2.xml"),
423                 ignore
424                 );
425 }
426