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