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