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