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 "interop_subtitle_asset.h"
39 #include "reel_subtitle_asset.h"
40 #include "reel_mono_picture_asset.h"
41 #include "reel_sound_asset.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>
55 using std::make_shared;
57 using std::shared_ptr;
58 using boost::optional;
62 stage (string, optional<boost::filesystem::path>)
75 dump_notes (vector<dcp::VerificationNote> const & notes)
78 std::cout << dcp::note_to_string(i) << "\n";
85 check_no_errors (boost::filesystem::path path)
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;
94 dump_notes (filtered_notes);
95 BOOST_CHECK (filtered_notes.empty());
101 pointer_to_id_in_vector (shared_ptr<T> needle, vector<shared_ptr<T>> haystack)
103 for (auto i: haystack) {
104 if (i->id() == needle->id()) {
109 return shared_ptr<T>();
115 note_handler (dcp::NoteType, std::string)
117 // std::cout << "> " << n << "\n";
123 check_combined (vector<boost::filesystem::path> inputs, boost::filesystem::path output)
125 dcp::DCP output_dcp (output);
128 dcp::EqualityOptions options;
129 options.load_font_nodes_can_differ = true;
131 for (auto i: inputs) {
132 dcp::DCP input_dcp (i);
135 BOOST_REQUIRE (input_dcp.cpls().size() == 1);
136 auto input_cpl = input_dcp.cpls().front();
138 auto output_cpl = pointer_to_id_in_vector (input_cpl, output_dcp.cpls());
139 BOOST_REQUIRE (output_cpl);
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));
150 BOOST_AUTO_TEST_CASE (combine_single_dcp_test)
152 using namespace boost::algorithm;
153 using namespace boost::filesystem;
154 boost::filesystem::path const out = "build/test/combine_single_dcp_test";
158 inputs.push_back ("test/ref/DCP/dcp_test1");
162 dcp::String::compose("libdcp %1", dcp::version),
163 dcp::String::compose("libdcp %1", dcp::version),
164 dcp::LocalTime().as_string(),
168 check_no_errors (out);
169 check_combined (inputs, out);
173 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_same_asset_filenames_test)
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";
179 auto second = make_simple ("build/test/combine_input2");
180 second->write_xml ();
184 inputs.push_back ("test/ref/DCP/dcp_test1");
185 inputs.push_back ("build/test/combine_input2");
186 dcp::combine (inputs, out);
188 check_no_errors (out);
189 check_combined (inputs, out);
193 BOOST_AUTO_TEST_CASE(combine_two_dcps_one_with_interop_subs_test)
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";
199 auto first = make_simple("build/test/combine_input1", 1, 24, dcp::Standard::INTEROP);
202 auto second = make_simple_with_interop_subs("build/test/combine_input2");
203 second->write_xml ();
206 vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
207 dcp::combine (inputs, out);
209 check_no_errors (out);
210 check_combined (inputs, out);
214 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_subs_test)
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";
220 auto first = make_simple_with_interop_subs ("build/test/combine_input1");
223 auto second = make_simple_with_interop_subs ("build/test/combine_input2");
224 second->write_xml ();
227 vector<path> inputs = {"build/test/combine_input1", "build/test/combine_input2"};
228 dcp::combine (inputs, out);
230 check_no_errors (out);
231 check_combined (inputs, out);
235 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_subs_test)
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";
241 auto first = make_simple_with_smpte_subs ("build/test/combine_input1");
244 auto second = make_simple_with_smpte_subs ("build/test/combine_input2");
245 second->write_xml ();
249 inputs.push_back ("build/test/combine_input1");
250 inputs.push_back ("build/test/combine_input2");
251 dcp::combine (inputs, out);
253 check_no_errors (out);
254 check_combined (inputs, out);
258 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_interop_ccaps_test)
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";
264 auto first = make_simple_with_interop_ccaps ("build/test/combine_input1");
267 auto second = make_simple_with_interop_ccaps ("build/test/combine_input2");
268 second->write_xml ();
272 inputs.push_back ("build/test/combine_input1");
273 inputs.push_back ("build/test/combine_input2");
274 dcp::combine (inputs, out);
276 check_no_errors (out);
277 check_combined (inputs, out);
281 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_smpte_ccaps_test)
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";
287 auto first = make_simple_with_smpte_ccaps ("build/test/combine_input1");
290 auto second = make_simple_with_smpte_ccaps ("build/test/combine_input2");
291 second->write_xml ();
295 inputs.push_back ("build/test/combine_input1");
296 inputs.push_back ("build/test/combine_input2");
297 dcp::combine (inputs, out);
299 check_no_errors (out);
300 check_combined (inputs, out);
304 BOOST_AUTO_TEST_CASE (combine_two_multi_reel_dcps)
306 using namespace boost::algorithm;
307 using namespace boost::filesystem;
308 boost::filesystem::path const out = "build/test/combine_two_multi_reel_dcps";
310 auto first = make_simple ("build/test/combine_input1", 4);
313 auto second = make_simple ("build/test/combine_input2", 4);
314 second->write_xml ();
318 inputs.push_back ("build/test/combine_input1");
319 inputs.push_back ("build/test/combine_input2");
320 dcp::combine (inputs, out);
322 check_no_errors (out);
323 check_combined (inputs, out);
327 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_shared_asset)
329 using namespace boost::filesystem;
330 boost::filesystem::path const out = "build/test/combine_two_dcps_with_shared_asset";
332 auto first = make_simple ("build/test/combine_input1", 1);
335 remove_all ("build/test/combine_input2");
336 auto second = make_shared<dcp::DCP>("build/test/combine_input2");
338 dcp::MXFMetadata mxf_meta;
339 mxf_meta.company_name = "OpenDCP";
340 mxf_meta.product_version = "0.0.25";
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")
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);
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());
358 second->write_xml ();
362 inputs.push_back ("build/test/combine_input1");
363 inputs.push_back ("build/test/combine_input2");
364 dcp::combine (inputs, out);
366 check_no_errors (out);
367 check_combined (inputs, out);
371 /** Two DCPs each with a copy of the exact same asset */
372 BOOST_AUTO_TEST_CASE (combine_two_dcps_with_duplicated_asset)
374 using namespace boost::filesystem;
375 boost::filesystem::path const out = "build/test/combine_two_dcps_with_duplicated_asset";
377 auto first = make_simple ("build/test/combine_input1", 1);
380 remove_all ("build/test/combine_input2");
381 auto second = make_shared<dcp::DCP>("build/test/combine_input2");
383 dcp::MXFMetadata mxf_meta;
384 mxf_meta.company_name = "OpenDCP";
385 mxf_meta.product_version = "0.0.25";
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")
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);
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());
407 second->write_xml ();
411 inputs.push_back ("build/test/combine_input1");
412 inputs.push_back ("build/test/combine_input2");
413 dcp::combine (inputs, out);
415 check_no_errors (out);
416 check_combined (inputs, out);
418 BOOST_REQUIRE (!boost::filesystem::exists(out / "my_great_audio.mxf"));
422 BOOST_AUTO_TEST_CASE (check_cpls_unchanged_after_combine)
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);
429 dcp::combine ({in}, out);
431 BOOST_REQUIRE_EQUAL (dcp->cpls().size(), 1U);
432 auto cpl = dcp->cpls()[0]->file();
434 check_file (*cpl, out / cpl->filename());
438 /** The combine process would write multiple fonts with the same ID (#2402) */
439 BOOST_AUTO_TEST_CASE(combine_multi_reel_subtitles)
441 boost::filesystem::path in = "build/test/combine_multi_reel_subtitles_in";
442 boost::filesystem::path out = "build/test/combine_multi_reel_subtitles_out";
445 auto dcp = make_simple(in, 2, 24, dcp::Standard::INTEROP);
447 dcp::ArrayData data1(4096);
448 memset(data1.data(), 0, data1.size());
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");
456 dcp::ArrayData data2(4096);
457 memset(data2.data(), 1, data1.size());
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");
465 auto reel_subs1 = make_shared<dcp::ReelInteropSubtitleAsset>(subs1, dcp::Fraction(24, 1), 240, 0);
466 dcp->cpls()[0]->reels()[0]->add(reel_subs1);
468 auto reel_subs2 = make_shared<dcp::ReelInteropSubtitleAsset>(subs2, dcp::Fraction(24, 1), 240, 0);
469 dcp->cpls()[0]->reels()[1]->add(reel_subs2);
473 dcp::combine({in}, out);
475 check_combined({in}, out);
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;
482 dump_notes(filtered_notes);
483 BOOST_CHECK(filtered_notes.empty());
487 /* XXX: same CPL names */
488 /* XXX: Interop PNG subs */