Write subtitle files in binary mode.
[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_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_with_interop_subs_test";
197
198         auto first = make_simple_with_interop_subs ("build/test/combine_input1");
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_smpte_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_smpte_subs_test";
218
219         auto first = make_simple_with_smpte_subs ("build/test/combine_input1");
220         first->write_xml ();
221
222         auto second = make_simple_with_smpte_subs ("build/test/combine_input2");
223         second->write_xml ();
224
225         remove_all (out);
226         vector<path> inputs;
227         inputs.push_back ("build/test/combine_input1");
228         inputs.push_back ("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_interop_ccaps_test)
237 {
238         using namespace boost::algorithm;
239         using namespace boost::filesystem;
240         boost::filesystem::path const out = "build/test/combine_two_dcps_with_interop_ccaps_test";
241
242         auto first = make_simple_with_interop_ccaps ("build/test/combine_input1");
243         first->write_xml ();
244
245         auto second = make_simple_with_interop_ccaps ("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_smpte_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_smpte_ccaps ("build/test/combine_input1");
266         first->write_xml ();
267
268         auto second = make_simple_with_smpte_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_multi_reel_dcps)
283 {
284         using namespace boost::algorithm;
285         using namespace boost::filesystem;
286         boost::filesystem::path const out = "build/test/combine_two_multi_reel_dcps";
287
288         auto first = make_simple ("build/test/combine_input1", 4);
289         first->write_xml ();
290
291         auto second = make_simple ("build/test/combine_input2", 4);
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_dcps_with_shared_asset)
306 {
307         using namespace boost::filesystem;
308         boost::filesystem::path const out = "build/test/combine_two_dcps_with_shared_asset";
309
310         auto first = make_simple ("build/test/combine_input1", 1);
311         first->write_xml ();
312
313         remove_all ("build/test/combine_input2");
314         auto second = make_shared<dcp::DCP>("build/test/combine_input2");
315
316         dcp::MXFMetadata mxf_meta;
317         mxf_meta.company_name = "OpenDCP";
318         mxf_meta.product_version = "0.0.25";
319
320         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
321         cpl->set_content_version (
322                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11","content-version-label-text")
323                 );
324         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
325         cpl->set_main_sound_sample_rate (48000);
326         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
327         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
328         cpl->set_version_number(1);
329
330         auto pic = make_shared<dcp::ReelMonoPictureAsset>(simple_picture("build/test/combine_input2", ""), 0);
331         auto sound = make_shared<dcp::ReelSoundAsset>(first->cpls().front()->reels().front()->main_sound()->asset(), 0);
332         auto reel = make_shared<dcp::Reel>(pic, sound);
333         reel->add (simple_markers());
334         cpl->add (reel);
335         second->add (cpl);
336         second->write_xml ();
337
338         remove_all (out);
339         vector<path> inputs;
340         inputs.push_back ("build/test/combine_input1");
341         inputs.push_back ("build/test/combine_input2");
342         dcp::combine (inputs, out);
343
344         check_no_errors (out);
345         check_combined (inputs, out);
346 }
347
348
349 /** Two DCPs each with a copy of the exact same asset */
350 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_duplicated_asset)
351 {
352         using namespace boost::filesystem;
353         boost::filesystem::path const out = "build/test/combine_two_dcps_with_duplicated_asset";
354
355         auto first = make_simple ("build/test/combine_input1", 1);
356         first->write_xml ();
357
358         remove_all ("build/test/combine_input2");
359         auto second = make_shared<dcp::DCP>("build/test/combine_input2");
360
361         dcp::MXFMetadata mxf_meta;
362         mxf_meta.company_name = "OpenDCP";
363         mxf_meta.product_version = "0.0.25";
364
365         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
366         cpl->set_content_version (
367                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11","content-version-label-text")
368                 );
369         cpl->set_main_sound_configuration ("L,C,R,Lfe,-,-");
370         cpl->set_main_sound_sample_rate (48000);
371         cpl->set_main_picture_stored_area (dcp::Size(1998, 1080));
372         cpl->set_main_picture_active_area (dcp::Size(1440, 1080));
373         cpl->set_version_number(1);
374
375         auto pic = make_shared<dcp::ReelMonoPictureAsset>(simple_picture("build/test/combine_input2", ""), 0);
376         auto first_sound_asset = first->cpls()[0]->reels()[0]->main_sound()->asset()->file();
377         BOOST_REQUIRE (first_sound_asset);
378         boost::filesystem::path second_sound_asset = "build/test/combine_input2/my_great_audio.mxf";
379         boost::filesystem::copy_file (*first_sound_asset, second_sound_asset);
380         auto sound = make_shared<dcp::ReelSoundAsset>(make_shared<dcp::SoundAsset>(second_sound_asset), 0);
381         auto reel = make_shared<dcp::Reel>(pic, sound);
382         reel->add (simple_markers());
383         cpl->add (reel);
384         second->add (cpl);
385         second->write_xml ();
386
387         remove_all (out);
388         vector<path> inputs;
389         inputs.push_back ("build/test/combine_input1");
390         inputs.push_back ("build/test/combine_input2");
391         dcp::combine (inputs, out);
392
393         check_no_errors (out);
394         check_combined (inputs, out);
395
396         BOOST_REQUIRE (!boost::filesystem::exists(out / "my_great_audio.mxf"));
397 }
398
399
400 BOOST_AUTO_TEST_CASE (check_cpls_unchanged_after_combine)
401 {
402         boost::filesystem::path in = "build/test/combine_one_dcp_with_composition_metadata_in";
403         boost::filesystem::path out = "build/test/combine_one_dcp_with_composition_metadata_out";
404         auto dcp = make_simple (in);
405         dcp->write_xml ();
406
407         dcp::combine ({in}, out);
408
409         BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
410         auto cpl = dcp->cpls()[0]->file();
411         BOOST_REQUIRE (cpl);
412         check_file (*cpl, out / cpl->filename());
413 }
414
415
416 /* XXX: same CPL names */
417 /* XXX: Interop PNG subs */