2 Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
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"
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>
56 using std::make_shared;
58 using std::shared_ptr;
59 using boost::optional;
63 stage (string, optional<boost::filesystem::path>)
76 dump_notes (vector<dcp::VerificationNote> const & notes)
79 std::cout << dcp::note_to_string(i) << "\n";
86 check_no_errors (boost::filesystem::path path)
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;
95 dump_notes (filtered_notes);
96 BOOST_CHECK (filtered_notes.empty());
102 pointer_to_id_in_vector (shared_ptr<T> needle, vector<shared_ptr<T>> haystack)
104 for (auto i: haystack) {
105 if (i->id() == needle->id()) {
110 return shared_ptr<T>();
116 note_handler (dcp::NoteType, std::string)
118 // std::cout << "> " << n << "\n";
124 check_combined (vector<boost::filesystem::path> inputs, boost::filesystem::path output)
126 dcp::DCP output_dcp (output);
129 dcp::EqualityOptions options;
130 options.load_font_nodes_can_differ = true;
132 for (auto i: inputs) {
133 dcp::DCP input_dcp (i);
136 BOOST_REQUIRE (input_dcp.cpls().size() == 1);
137 auto input_cpl = input_dcp.cpls().front();
139 auto output_cpl = pointer_to_id_in_vector (input_cpl, output_dcp.cpls());
140 BOOST_REQUIRE (output_cpl);
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));
151 BOOST_AUTO_TEST_CASE (combine_single_dcp_test)
153 using namespace boost::algorithm;
154 using namespace boost::filesystem;
155 boost::filesystem::path const out = "build/test/combine_single_dcp_test";
159 inputs.push_back ("test/ref/DCP/dcp_test1");
163 dcp::String::compose("libdcp %1", dcp::version),
164 dcp::String::compose("libdcp %1", dcp::version),
165 dcp::LocalTime().as_string(),
169 check_no_errors (out);
170 check_combined (inputs, out);
174 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_same_asset_filenames_test)
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";
180 auto second = make_simple ("build/test/combine_input2");
181 second->write_xml ();
185 inputs.push_back ("test/ref/DCP/dcp_test1");
186 inputs.push_back ("build/test/combine_input2");
187 dcp::combine (inputs, out);
189 check_no_errors (out);
190 check_combined (inputs, out);
194 BOOST_AUTO_TEST_CASE(combine_two_dcps_one_with_interop_subs_test)
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";
200 auto first = make_simple("build/test/combine_input1", 1, 24, dcp::Standard::INTEROP);
203 auto second = make_simple_with_interop_subs("build/test/combine_input2");
204 second->write_xml ();
207 vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
208 dcp::combine (inputs, out);
210 check_no_errors (out);
211 check_combined (inputs, out);
215 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_subs_test)
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";
221 auto first = make_simple_with_interop_subs ("build/test/combine_input1");
224 auto second = make_simple_with_interop_subs ("build/test/combine_input2");
225 second->write_xml ();
228 vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
229 dcp::combine (inputs, out);
231 check_no_errors (out);
232 check_combined (inputs, out);
236 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_subs_test)
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";
242 auto first = make_simple_with_smpte_subs ("build/test/combine_input1");
245 auto second = make_simple_with_smpte_subs ("build/test/combine_input2");
246 second->write_xml ();
250 inputs.push_back ("build/test/combine_input1");
251 inputs.push_back ("build/test/combine_input2");
252 dcp::combine (inputs, out);
254 check_no_errors (out);
255 check_combined (inputs, out);
259 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_ccaps_test)
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";
265 auto first = make_simple_with_interop_ccaps ("build/test/combine_input1");
268 auto second = make_simple_with_interop_ccaps ("build/test/combine_input2");
269 second->write_xml ();
273 inputs.push_back ("build/test/combine_input1");
274 inputs.push_back ("build/test/combine_input2");
275 dcp::combine (inputs, out);
277 check_no_errors (out);
278 check_combined (inputs, out);
282 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_ccaps_test)
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";
288 auto first = make_simple_with_smpte_ccaps ("build/test/combine_input1");
291 auto second = make_simple_with_smpte_ccaps ("build/test/combine_input2");
292 second->write_xml ();
296 inputs.push_back ("build/test/combine_input1");
297 inputs.push_back ("build/test/combine_input2");
298 dcp::combine (inputs, out);
300 check_no_errors (out);
301 check_combined (inputs, out);
305 BOOST_AUTO_TEST_CASE (combine_two_multi_reel_dcps)
307 using namespace boost::algorithm;
308 using namespace boost::filesystem;
309 boost::filesystem::path const out = "build/test/combine_two_multi_reel_dcps";
311 auto first = make_simple ("build/test/combine_input1", 4);
314 auto second = make_simple ("build/test/combine_input2", 4);
315 second->write_xml ();
319 inputs.push_back ("build/test/combine_input1");
320 inputs.push_back ("build/test/combine_input2");
321 dcp::combine (inputs, out);
323 check_no_errors (out);
324 check_combined (inputs, out);
328 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_shared_asset)
330 using namespace boost::filesystem;
331 boost::filesystem::path const out = "build/test/combine_two_dcps_with_shared_asset";
333 auto first = make_simple ("build/test/combine_input1", 1);
336 remove_all ("build/test/combine_input2");
337 auto second = make_shared<dcp::DCP>("build/test/combine_input2");
339 dcp::MXFMetadata mxf_meta;
340 mxf_meta.company_name = "OpenDCP";
341 mxf_meta.product_version = "0.0.25";
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")
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);
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());
359 second->write_xml ();
363 inputs.push_back ("build/test/combine_input1");
364 inputs.push_back ("build/test/combine_input2");
365 dcp::combine (inputs, out);
367 check_no_errors (out);
368 check_combined (inputs, out);
372 /** Two DCPs each with a copy of the exact same asset */
373 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_duplicated_asset)
375 using namespace boost::filesystem;
376 boost::filesystem::path const out = "build/test/combine_two_dcps_with_duplicated_asset";
378 auto first = make_simple ("build/test/combine_input1", 1);
381 remove_all ("build/test/combine_input2");
382 auto second = make_shared<dcp::DCP>("build/test/combine_input2");
384 dcp::MXFMetadata mxf_meta;
385 mxf_meta.company_name = "OpenDCP";
386 mxf_meta.product_version = "0.0.25";
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")
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);
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());
408 second->write_xml ();
412 inputs.push_back ("build/test/combine_input1");
413 inputs.push_back ("build/test/combine_input2");
414 dcp::combine (inputs, out);
416 check_no_errors (out);
417 check_combined (inputs, out);
419 BOOST_REQUIRE (!boost::filesystem::exists(out / "my_great_audio.mxf"));
423 BOOST_AUTO_TEST_CASE (check_cpls_unchanged_after_combine)
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);
430 dcp::combine ({in}, out);
432 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
433 auto cpl = dcp->cpls()[0]->file();
435 check_file (*cpl, out / cpl->filename());
439 /** The combine process would write multiple fonts with the same ID (#2402) */
440 BOOST_AUTO_TEST_CASE(combine_multi_reel_subtitles)
442 boost::filesystem::path in = "build/test/combine_multi_reel_subtitles_in";
443 boost::filesystem::path out = "build/test/combine_multi_reel_subtitles_out";
446 auto dcp = make_simple(in, 2, 24, dcp::Standard::INTEROP);
448 dcp::ArrayData data1(4096);
449 memset(data1.data(), 0, data1.size());
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");
457 dcp::ArrayData data2(4096);
458 memset(data2.data(), 1, data1.size());
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");
466 auto reel_subs1 = make_shared<dcp::ReelInteropSubtitleAsset>(subs1, dcp::Fraction(24, 1), 240, 0);
467 dcp->cpls()[0]->reels()[0]->add(reel_subs1);
469 auto reel_subs2 = make_shared<dcp::ReelInteropSubtitleAsset>(subs2, dcp::Fraction(24, 1), 240, 0);
470 dcp->cpls()[0]->reels()[1]->add(reel_subs2);
474 dcp::combine({in}, out);
476 check_combined({in}, out);
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;
483 dump_notes(filtered_notes);
484 BOOST_CHECK(filtered_notes.empty());
488 /* XXX: same CPL names */
489 /* XXX: Interop PNG subs */