Add asset_hashes_can_differ option to the equality checks.
[libdcp.git] / test / combine_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 "combine.h"
36 #include "cpl.h"
37 #include "dcp.h"
38 #include "interop_subtitle_asset.h"
39 #include "reel_subtitle_asset.h"
40 #include "reel_mono_picture_asset.h"
41 #include "reel_sound_asset.h"
42 #include "test.h"
43 #include "types.h"
44 #include "verify.h"
45 #include "reel_interop_subtitle_asset.h"
46 #include "reel_markers_asset.h"
47 #include <boost/algorithm/string.hpp>
48 #include <boost/optional.hpp>
49 #include <boost/test/unit_test.hpp>
50 #include <iostream>
51
52
53 using std::list;
54 using std::string;
55 using std::make_shared;
56 using std::vector;
57 using std::shared_ptr;
58 using boost::optional;
59
60
61 static void
62 stage (string, optional<boost::filesystem::path>)
63 {
64 }
65
66
67 static void
68 progress (float)
69 {
70 }
71
72
73 static
74 void
75 dump_notes (vector<dcp::VerificationNote> const & notes)
76 {
77         for (auto i: notes) {
78                 std::cout << dcp::note_to_string(i) << "\n";
79         }
80 }
81
82
83 static
84 void
85 check_no_errors (boost::filesystem::path path)
86 {
87         vector<boost::filesystem::path> directories;
88         directories.push_back (path);
89         auto notes = dcp::verify(directories, &stage, &progress, {}, xsd_test);
90         vector<dcp::VerificationNote> filtered_notes;
91         std::copy_if (notes.begin(), notes.end(), std::back_inserter(filtered_notes), [](dcp::VerificationNote const& i) {
92                 return i.code() != dcp::VerificationNote::Code::INVALID_STANDARD && i.code() != dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION;
93         });
94         dump_notes (filtered_notes);
95         BOOST_CHECK (filtered_notes.empty());
96 }
97
98
99 template <class T>
100 shared_ptr<T>
101 pointer_to_id_in_vector (shared_ptr<T> needle, vector<shared_ptr<T>> haystack)
102 {
103         for (auto i: haystack) {
104                 if (i->id() == needle->id()) {
105                         return i;
106                 }
107         }
108
109         return shared_ptr<T>();
110 }
111
112
113 static
114 void
115 note_handler (dcp::NoteType, std::string)
116 {
117         // std::cout << "> " << n << "\n";
118 }
119
120
121 static
122 void
123 check_combined (vector<boost::filesystem::path> inputs, boost::filesystem::path output)
124 {
125         dcp::DCP output_dcp (output);
126         output_dcp.read ();
127
128         dcp::EqualityOptions options;
129         options.load_font_nodes_can_differ = true;
130
131         for (auto i: inputs) {
132                 dcp::DCP input_dcp (i);
133                 input_dcp.read ();
134
135                 BOOST_REQUIRE (input_dcp.cpls().size() == 1);
136                 auto input_cpl = input_dcp.cpls().front();
137
138                 auto output_cpl = pointer_to_id_in_vector (input_cpl, output_dcp.cpls());
139                 BOOST_REQUIRE (output_cpl);
140
141                 for (auto i: input_dcp.assets(true)) {
142                         auto o = pointer_to_id_in_vector(i, output_dcp.assets());
143                         BOOST_REQUIRE_MESSAGE (o, "Could not find " << i->id() << " in combined DCP.");
144                         BOOST_CHECK (i->equals(o, options, note_handler));
145                 }
146         }
147 }
148
149
150 BOOST_AUTO_TEST_CASE (combine_single_dcp_test)
151 {
152         using namespace boost::algorithm;
153         using namespace boost::filesystem;
154         boost::filesystem::path const out = "build/test/combine_single_dcp_test";
155
156         remove_all (out);
157         vector<path> inputs;
158         inputs.push_back ("test/ref/DCP/dcp_test1");
159         dcp::combine (
160                 inputs,
161                 out,
162                 dcp::String::compose("libdcp %1", dcp::version),
163                 dcp::String::compose("libdcp %1", dcp::version),
164                 dcp::LocalTime().as_string(),
165                 "A Test DCP"
166                 );
167
168         check_no_errors (out);
169         check_combined (inputs, out);
170 }
171
172
173 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_same_asset_filenames_test)
174 {
175         using namespace boost::algorithm;
176         using namespace boost::filesystem;
177         boost::filesystem::path const out = "build/test/combine_two_dcps_with_same_asset_filenames_test";
178
179         auto second = make_simple ("build/test/combine_input2");
180         second->write_xml ();
181
182         remove_all (out);
183         vector<path> inputs;
184         inputs.push_back ("test/ref/DCP/dcp_test1");
185         inputs.push_back ("build/test/combine_input2");
186         dcp::combine (inputs, out);
187
188         check_no_errors (out);
189         check_combined (inputs, out);
190 }
191
192
193 BOOST_AUTO_TEST_CASE(combine_two_dcps_one_with_interop_subs_test)
194 {
195         using namespace boost::algorithm;
196         using namespace boost::filesystem;
197         boost::filesystem::path const out = "build/test/combine_two_dcps_one_with_interop_subs_test";
198
199         auto first = make_simple("build/test/combine_input1", 1, 24, dcp::Standard::INTEROP);
200         first->write_xml ();
201
202         auto second = make_simple_with_interop_subs("build/test/combine_input2");
203         second->write_xml ();
204
205         remove_all (out);
206         vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
207         dcp::combine (inputs, out);
208
209         check_no_errors (out);
210         check_combined (inputs, out);
211 }
212
213
214 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_subs_test)
215 {
216         using namespace boost::algorithm;
217         using namespace boost::filesystem;
218         boost::filesystem::path const out = "build/test/combine_two_dcps_with_interop_subs_test";
219
220         auto first = make_simple_with_interop_subs ("build/test/combine_input1");
221         first->write_xml ();
222
223         auto second = make_simple_with_interop_subs ("build/test/combine_input2");
224         second->write_xml ();
225
226         remove_all (out);
227         vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
228         dcp::combine (inputs, out);
229
230         check_no_errors (out);
231         check_combined (inputs, out);
232 }
233
234
235 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_subs_test)
236 {
237         using namespace boost::algorithm;
238         using namespace boost::filesystem;
239         boost::filesystem::path const out = "build/test/combine_two_dcps_with_smpte_subs_test";
240
241         auto first = make_simple_with_smpte_subs ("build/test/combine_input1");
242         first->write_xml ();
243
244         auto second = make_simple_with_smpte_subs ("build/test/combine_input2");
245         second->write_xml ();
246
247         remove_all (out);
248         vector<path> inputs;
249         inputs.push_back ("build/test/combine_input1");
250         inputs.push_back ("build/test/combine_input2");
251         dcp::combine (inputs, out);
252
253         check_no_errors (out);
254         check_combined (inputs, out);
255 }
256
257
258 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_ccaps_test)
259 {
260         using namespace boost::algorithm;
261         using namespace boost::filesystem;
262         boost::filesystem::path const out = "build/test/combine_two_dcps_with_interop_ccaps_test";
263
264         auto first = make_simple_with_interop_ccaps ("build/test/combine_input1");
265         first->write_xml ();
266
267         auto second = make_simple_with_interop_ccaps ("build/test/combine_input2");
268         second->write_xml ();
269
270         remove_all (out);
271         vector<path> inputs;
272         inputs.push_back ("build/test/combine_input1");
273         inputs.push_back ("build/test/combine_input2");
274         dcp::combine (inputs, out);
275
276         check_no_errors (out);
277         check_combined (inputs, out);
278 }
279
280
281 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_ccaps_test)
282 {
283         using namespace boost::algorithm;
284         using namespace boost::filesystem;
285         boost::filesystem::path const out = "build/test/combine_two_dcps_with_interop_ccaps_test";
286
287         auto first = make_simple_with_smpte_ccaps ("build/test/combine_input1");
288         first->write_xml ();
289
290         auto second = make_simple_with_smpte_ccaps ("build/test/combine_input2");
291         second->write_xml ();
292
293         remove_all (out);
294         vector<path> inputs;
295         inputs.push_back ("build/test/combine_input1");
296         inputs.push_back ("build/test/combine_input2");
297         dcp::combine (inputs, out);
298
299         check_no_errors (out);
300         check_combined (inputs, out);
301 }
302
303
304 BOOST_AUTO_TEST_CASE (combine_two_multi_reel_dcps)
305 {
306         using namespace boost::algorithm;
307         using namespace boost::filesystem;
308         boost::filesystem::path const out = "build/test/combine_two_multi_reel_dcps";
309
310         auto first = make_simple ("build/test/combine_input1", 4);
311         first->write_xml ();
312
313         auto second = make_simple ("build/test/combine_input2", 4);
314         second->write_xml ();
315
316         remove_all (out);
317         vector<path> inputs;
318         inputs.push_back ("build/test/combine_input1");
319         inputs.push_back ("build/test/combine_input2");
320         dcp::combine (inputs, out);
321
322         check_no_errors (out);
323         check_combined (inputs, out);
324 }
325
326
327 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_shared_asset)
328 {
329         using namespace boost::filesystem;
330         boost::filesystem::path const out = "build/test/combine_two_dcps_with_shared_asset";
331
332         auto first = make_simple ("build/test/combine_input1", 1);
333         first->write_xml ();
334
335         remove_all ("build/test/combine_input2");
336         auto second = make_shared<dcp::DCP>("build/test/combine_input2");
337
338         dcp::MXFMetadata mxf_meta;
339         mxf_meta.company_name = "OpenDCP";
340         mxf_meta.product_version = "0.0.25";
341
342         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
343         cpl->set_content_version (
344                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11","content-version-label-text")
345                 );
346         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
347         cpl->set_main_sound_sample_rate (48000);
348         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
349         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
350         cpl->set_version_number(1);
351
352         auto pic = make_shared<dcp::ReelMonoPictureAsset>(simple_picture("build/test/combine_input2", ""), 0);
353         auto sound = make_shared<dcp::ReelSoundAsset>(first->cpls().front()->reels().front()->main_sound()->asset(), 0);
354         auto reel = make_shared<dcp::Reel>(pic, sound);
355         reel->add (simple_markers());
356         cpl->add (reel);
357         second->add (cpl);
358         second->write_xml ();
359
360         remove_all (out);
361         vector<path> inputs;
362         inputs.push_back ("build/test/combine_input1");
363         inputs.push_back ("build/test/combine_input2");
364         dcp::combine (inputs, out);
365
366         check_no_errors (out);
367         check_combined (inputs, out);
368 }
369
370
371 /** Two DCPs each with a copy of the exact same asset */
372 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_duplicated_asset)
373 {
374         using namespace boost::filesystem;
375         boost::filesystem::path const out = "build/test/combine_two_dcps_with_duplicated_asset";
376
377         auto first = make_simple ("build/test/combine_input1", 1);
378         first->write_xml ();
379
380         remove_all ("build/test/combine_input2");
381         auto second = make_shared<dcp::DCP>("build/test/combine_input2");
382
383         dcp::MXFMetadata mxf_meta;
384         mxf_meta.company_name = "OpenDCP";
385         mxf_meta.product_version = "0.0.25";
386
387         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
388         cpl->set_content_version (
389                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11","content-version-label-text")
390                 );
391         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,C,R,LFE,-,-"));
392         cpl->set_main_sound_sample_rate (48000);
393         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
394         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
395         cpl->set_version_number(1);
396
397         auto pic = make_shared<dcp::ReelMonoPictureAsset>(simple_picture("build/test/combine_input2", ""), 0);
398         auto first_sound_asset = first->cpls()[0]->reels()[0]->main_sound()->asset()->file();
399         BOOST_REQUIRE (first_sound_asset);
400         boost::filesystem::path second_sound_asset = "build/test/combine_input2/my_great_audio.mxf";
401         boost::filesystem::copy_file (*first_sound_asset, second_sound_asset);
402         auto sound = make_shared<dcp::ReelSoundAsset>(make_shared<dcp::SoundAsset>(second_sound_asset), 0);
403         auto reel = make_shared<dcp::Reel>(pic, sound);
404         reel->add (simple_markers());
405         cpl->add (reel);
406         second->add (cpl);
407         second->write_xml ();
408
409         remove_all (out);
410         vector<path> inputs;
411         inputs.push_back ("build/test/combine_input1");
412         inputs.push_back ("build/test/combine_input2");
413         dcp::combine (inputs, out);
414
415         check_no_errors (out);
416         check_combined (inputs, out);
417
418         BOOST_REQUIRE (!boost::filesystem::exists(out / "my_great_audio.mxf"));
419 }
420
421
422 BOOST_AUTO_TEST_CASE (check_cpls_unchanged_after_combine)
423 {
424         boost::filesystem::path in = "build/test/combine_one_dcp_with_composition_metadata_in";
425         boost::filesystem::path out = "build/test/combine_one_dcp_with_composition_metadata_out";
426         auto dcp = make_simple (in);
427         dcp->write_xml ();
428
429         dcp::combine ({in}, out);
430
431         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
432         auto cpl = dcp->cpls()[0]->file();
433         BOOST_REQUIRE (cpl);
434         check_file (*cpl, out / cpl->filename());
435 }
436
437
438 /** The combine process would write multiple fonts with the same ID (#2402) */
439 BOOST_AUTO_TEST_CASE(combine_multi_reel_subtitles)
440 {
441         boost::filesystem::path in = "build/test/combine_multi_reel_subtitles_in";
442         boost::filesystem::path out = "build/test/combine_multi_reel_subtitles_out";
443         remove_all(out);
444
445         auto dcp = make_simple(in, 2, 24, dcp::Standard::INTEROP);
446
447         dcp::ArrayData data1(4096);
448         memset(data1.data(), 0, data1.size());
449
450         auto subs1 = make_shared<dcp::InteropSubtitleAsset>();
451         subs1->add(simple_subtitle());
452         boost::filesystem::create_directory(in / "subs1");
453         subs1->add_font("afont1", data1);
454         subs1->write(in / "subs1" / "subs1.xml");
455
456         dcp::ArrayData data2(4096);
457         memset(data2.data(), 1, data1.size());
458
459         auto subs2 = make_shared<dcp::InteropSubtitleAsset>();
460         subs2->add(simple_subtitle());
461         boost::filesystem::create_directory(in / "subs2");
462         subs2->add_font("afont2", data2);
463         subs2->write(in / "subs2" / "subs2.xml");
464
465         auto reel_subs1 = make_shared<dcp::ReelInteropSubtitleAsset>(subs1, dcp::Fraction(24, 1), 240, 0);
466         dcp->cpls()[0]->reels()[0]->add(reel_subs1);
467
468         auto reel_subs2 = make_shared<dcp::ReelInteropSubtitleAsset>(subs2, dcp::Fraction(24, 1), 240, 0);
469         dcp->cpls()[0]->reels()[1]->add(reel_subs2);
470
471         dcp->write_xml();
472
473         dcp::combine({in}, out);
474
475         check_combined({in}, out);
476
477         auto notes = dcp::verify({out}, &stage, &progress, {}, xsd_test);
478         vector<dcp::VerificationNote> filtered_notes;
479         std::copy_if(notes.begin(), notes.end(), std::back_inserter(filtered_notes), [](dcp::VerificationNote const& i) {
480                 return i.code() != dcp::VerificationNote::Code::INVALID_STANDARD && i.code() != dcp::VerificationNote::Code::MISMATCHED_PKL_ANNOTATION_TEXT_WITH_CPL;
481         });
482         dump_notes(filtered_notes);
483         BOOST_CHECK(filtered_notes.empty());
484 }
485
486
487 /* XXX: same CPL names */
488 /* XXX: Interop PNG subs */