From: Carl Hetherington Date: Fri, 17 Feb 2023 23:15:30 +0000 (+0100) Subject: Add dcpomatic2_map tool (#2445). X-Git-Tag: v2.16.56~7 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=3a1ddb9e182be28a22c5bbc85c06ee6629b72fe6 Add dcpomatic2_map tool (#2445). --- diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh index 7b2f330a2..377550891 100644 --- a/platform/osx/make_dmg.sh +++ b/platform/osx/make_dmg.sh @@ -487,6 +487,7 @@ if [[ "$BUILD" == *main* ]]; then copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2 "$approot/MacOS" copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_cli "$approot/MacOS" copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_create "$approot/MacOS" + copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_map "$approot/MacOS" copy $ROOT bin/ffprobe "$approot/MacOS" copy $ROOT src/openssl/apps/openssl "$approot/MacOS" copy_verify diff --git a/platform/windows/wscript b/platform/windows/wscript index b87344646..e54455427 100644 --- a/platform/windows/wscript +++ b/platform/windows/wscript @@ -13,6 +13,7 @@ def write_installer(bits, dcpomatic_version, debug, disk): ('playlist', 'Playlist Editor'), ('combiner', 'Combiner'), ('editor', 'Editor'), + ('map', 'Map'), ] if disk: diff --git a/run/dcpomatic_map b/run/dcpomatic_map new file mode 100755 index 000000000..f81be3067 --- /dev/null +++ b/run/dcpomatic_map @@ -0,0 +1,14 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source $DIR/environment + +if [ "$1" == "--debug" ]; then + shift + gdb --args build/src/tools/dcpomatic2_map "$@" +elif [ "$1" == "--valgrind" ]; then + shift + valgrind --tool="memcheck" --leak-check=full --show-reachable=yes build/src/tools/dcpomatic2_map "$@" +else + build/src/tools/dcpomatic2_map "$@" +fi diff --git a/src/lib/map_cli.cc b/src/lib/map_cli.cc new file mode 100644 index 000000000..1b99afa94 --- /dev/null +++ b/src/lib/map_cli.cc @@ -0,0 +1,287 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "compose.hpp" +#include "config.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::string; +using std::make_shared; +using std::vector; +using boost::optional; + + +static void +help(std::function out) +{ + out(String::compose("Syntax: %1 [OPTION} [ ... ]", program_name)); + out(" -V, --version show libdcp version"); + out(" -h, --help show this help"); + out(" -o, --output output directory"); + out(" -l, --hard-link using hard links instead of copying"); + out(" -s, --soft-link using soft links instead of copying"); + out(" -d, --assets-dir look in this directory for assets (can be given more than once)"); + out(" -r, --rename rename all files to ."); +} + + +optional +map_cli(int argc, char* argv[], std::function out) +{ + optional output_dir; + bool hard_link = false; + bool soft_link = false; + bool rename = false; + vector assets_dir; + + /* This makes it possible to call getopt several times in the same executable, for tests */ + optind = 0; + + int option_index = 0; + while (true) { + static struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "output", required_argument, 0, 'o' }, + { "hard-link", no_argument, 0, 'l' }, + { "soft-link", no_argument, 0, 's' }, + { "assets-dir", required_argument, 0, 'd' }, + { "rename", no_argument, 0, 'r' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long(argc, argv, "ho:lsd:r", long_options, &option_index); + + if (c == -1) { + break; + } else if (c == '?' || c == ':') { + exit(EXIT_FAILURE); + } + + switch (c) { + case 'h': + help(out); + exit(EXIT_SUCCESS); + case 'o': + output_dir = optarg; + break; + case 'l': + hard_link = true; + break; + case 's': + soft_link = true; + break; + case 'd': + assets_dir.push_back(optarg); + break; + case 'r': + rename = true; + break; + } + } + + program_name = argv[0]; + + if (argc <= optind) { + help(out); + exit(EXIT_FAILURE); + } + + vector cpl_filenames; + for (int i = optind; i < argc; ++i) { + cpl_filenames.push_back(argv[i]); + } + + if (cpl_filenames.empty()) { + return string{"No CPL specified."}; + } + + if (!output_dir) { + return string{"Missing -o or --output"}; + } + + if (boost::filesystem::exists(*output_dir)) { + return String::compose("Output directory %1 already exists.", *output_dir); + } + + if (hard_link && soft_link) { + return string{"Specify either -s,--soft-link or -l,--hard-link, not both."}; + } + + boost::system::error_code ec; + boost::filesystem::create_directory(*output_dir, ec); + if (ec) { + return String::compose("Could not create output directory %1: %2", *output_dir, ec.message()); + } + + /* Find all the assets in the asset directories. This assumes that the asset directories are in fact + * DCPs (with AssetMaps and so on). We could search for assets ourselves here but interop fonts are + * a little tricky because they don't contain their own UUID within the DCP. + */ + vector> assets; + for (auto dir: assets_dir) { + dcp::DCP dcp(dir); + dcp.read(); + auto dcp_assets = dcp.assets(true); + std::copy(dcp_assets.begin(), dcp_assets.end(), back_inserter(assets)); + } + + dcp::DCP dcp(*output_dir); + + /* Find all the CPLs */ + vector> cpls; + for (auto filename: cpl_filenames) { + try { + auto cpl = make_shared(filename); + cpl->resolve_refs(assets); + cpls.push_back(cpl); + } catch (std::exception& e) { + return String::compose("Could not read CPL %1: %2", filename, e.what()); + } + } + + class CopyError : public std::runtime_error + { + public: + CopyError(std::string message) : std::runtime_error(message) {} + }; + + auto maybe_copy = [&assets, output_dir]( + string asset_id, + bool rename, + bool hard_link, + bool soft_link, + boost::optional extra = boost::none + ) { + auto iter = std::find_if(assets.begin(), assets.end(), [asset_id](shared_ptr a) { return a->id() == asset_id; }); + if (iter != assets.end()) { + DCP_ASSERT((*iter)->file()); + + auto const input_path = (*iter)->file().get(); + boost::filesystem::path output_path = *output_dir; + if (extra) { + output_path /= *extra; + } + + if (rename) { + output_path /= String::compose("%1%2", (*iter)->id(), boost::filesystem::extension((*iter)->file().get())); + (*iter)->rename_file(output_path); + } else { + output_path /= (*iter)->file()->filename(); + } + + boost::filesystem::create_directories(output_path.parent_path()); + + boost::system::error_code ec; + if (hard_link) { + boost::filesystem::create_hard_link(input_path, output_path, ec); + if (ec) { + throw CopyError(String::compose("Could not hard-link asset %1: %2", input_path.string(), ec.message())); + } + } else if (soft_link) { + boost::filesystem::create_symlink(input_path, output_path, ec); + if (ec) { + throw CopyError(String::compose("Could not soft-link asset %1: %2", input_path.string(), ec.message())); + } + } else { + boost::filesystem::copy_file(input_path, output_path, ec); + if (ec) { + throw CopyError(String::compose("Could not copy asset %1: %2", input_path.string(), ec.message())); + } + } + (*iter)->set_file(output_path); + } else { + boost::system::error_code ec; + boost::filesystem::remove_all(*output_dir, ec); + throw CopyError(String::compose("Could not find required asset %1", asset_id)); + } + }; + + auto maybe_copy_from_reel = [output_dir, &maybe_copy]( + shared_ptr asset, + bool rename, + bool hard_link, + bool soft_link, + boost::optional extra = boost::none + ) { + if (asset && asset->asset_ref().resolved()) { + maybe_copy(asset->asset_ref().id(), rename, hard_link, soft_link, extra); + } + }; + + /* Copy assets that the CPLs need */ + try { + for (auto cpl: cpls) { + for (auto reel: cpl->reels()) { + maybe_copy_from_reel(reel->main_picture(), rename, hard_link, soft_link); + maybe_copy_from_reel(reel->main_sound(), rename, hard_link, soft_link); + boost::optional extra; + if (reel->main_subtitle()) { + auto interop = dynamic_pointer_cast(reel->main_subtitle()->asset()); + if (interop) { + extra = interop->id(); + for (auto font_asset: interop->font_assets()) { + maybe_copy(font_asset->id(), rename, hard_link, soft_link, extra); + } + } + } + maybe_copy_from_reel(reel->main_subtitle(), rename, hard_link, soft_link, extra); + for (auto ccap: reel->closed_captions()) { + maybe_copy_from_reel(ccap, rename, hard_link, soft_link); + } + maybe_copy_from_reel(reel->atmos(), rename, hard_link, soft_link); + } + + dcp.add(cpl); + } + } catch (CopyError& e) { + return string{e.what()}; + } + + dcp.resolve_refs(assets); + dcp.set_annotation_text(cpls[0]->annotation_text().get_value_or("")); + dcp.write_xml(Config::instance()->signer_chain()); + + return {}; +} + diff --git a/src/lib/map_cli.h b/src/lib/map_cli.h new file mode 100644 index 000000000..b26054541 --- /dev/null +++ b/src/lib/map_cli.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include +#include + + +extern boost::optional map_cli(int argc, char* argv[], std::function out); + diff --git a/src/lib/wscript b/src/lib/wscript index de7b947c2..00b6b7ed8 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -145,6 +145,7 @@ sources = """ log.cc log_entry.cc make_dcp.cc + map_cli.cc maths_util.cc memory_util.cc mid_side_decoder.cc diff --git a/src/tools/dcpomatic_map.cc b/src/tools/dcpomatic_map.cc new file mode 100644 index 000000000..2817fdd75 --- /dev/null +++ b/src/tools/dcpomatic_map.cc @@ -0,0 +1,49 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +/** @file src/tools/dcpomatic_map.cc + * @brief Command-line program to assemble new DCPs from CPLs and assets. + */ + + +#include "lib/cross.h" +#include "lib/map_cli.h" +#include "lib/util.h" +#include + + +int +main(int argc, char* argv[]) +{ + ArgFixer fixer(argc, argv); + + dcpomatic_setup_path_encoding(); + dcpomatic_setup(); + + auto error = map_cli(fixer.argc(), fixer.argv(), [](std::string s) { std::cout << s << "\n"; }); + if (error) { + std::cerr << *error << "\n"; + exit (EXIT_FAILURE); + } + + return 0; +} + diff --git a/src/tools/wscript b/src/tools/wscript index 9a3b95a79..c3b2b5fe0 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -42,7 +42,7 @@ def build(bld): if bld.env.TARGET_LINUX: uselib += 'DL ' - cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create'] + cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create', 'dcpomatic_map'] if bld.env.ENABLE_DISK and not bld.env.DISABLE_GUI: cli_tools.append('dcpomatic_disk_writer') diff --git a/test/map_cli_test.cc b/test/map_cli_test.cc new file mode 100644 index 000000000..3e6abc059 --- /dev/null +++ b/test/map_cli_test.cc @@ -0,0 +1,355 @@ +/* + Copyright (C) 2023 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "lib/config.h" +#include "lib/content.h" +#include "lib/dcp_content.h" +#include "lib/content_factory.h" +#include "lib/film.h" +#include "lib/map_cli.h" +#include "test.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using std::dynamic_pointer_cast; +using std::make_shared; +using std::shared_ptr; +using std::string; +using std::vector; +using boost::optional; + + +static +optional +run(vector const& args, vector& output) +{ + vector argv(args.size() + 1); + for (auto i = 0U; i < args.size(); ++i) { + argv[i] = const_cast(args[i].c_str()); + } + argv[args.size()] = nullptr; + + auto error = map_cli(args.size(), argv.data(), [&output](string s) { output.push_back(s); }); + if (error) { + std::cout << *error << "\n"; + } + + return error; +} + + +static +boost::filesystem::path +find_prefix(boost::filesystem::path dir, string prefix) +{ + auto iter = std::find_if(boost::filesystem::directory_iterator(dir), boost::filesystem::directory_iterator(), [prefix](boost::filesystem::path const& p) { + return boost::starts_with(p.filename().string(), prefix); + }); + + BOOST_REQUIRE(iter != boost::filesystem::directory_iterator()); + return iter->path(); +} + + +static +boost::filesystem::path +find_cpl(boost::filesystem::path dir) +{ + return find_prefix(dir, "cpl_"); +} + + +/** Map a single DCP into a new DCP */ +BOOST_AUTO_TEST_CASE(map_simple_dcp_copy) +{ + string const name = "map_simple_dcp_copy"; + string const out = String::compose("build/test/%1_out", name); + + auto content = content_factory("test/data/flat_red.png"); + auto film = new_test_film2(name + "_in", content); + make_and_verify_dcp(film); + + vector const args = { + "map_cli", + "-o", out, + "-d", film->dir(film->dcp_name()).string(), + find_cpl(film->dir(film->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, {}); + + BOOST_CHECK(boost::filesystem::is_regular_file(find_prefix(out, "j2c_"))); + BOOST_CHECK(boost::filesystem::is_regular_file(find_prefix(out, "pcm_"))); +} + + +/** Map a single DCP into a new DCP using the symlink option */ +BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_symlinks) +{ + string const name = "map_simple_dcp_copy_with_symlinks"; + string const out = String::compose("build/test/%1_out", name); + + auto content = content_factory("test/data/flat_red.png"); + auto film = new_test_film2(name + "_in", content); + make_and_verify_dcp(film); + + vector const args = { + "map_cli", + "-o", out, + "-d", film->dir(film->dcp_name()).string(), + "-s", + find_cpl(film->dir(film->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + /* We can't verify this DCP because the symlinks will make it fail + * (as it should be, I think). + */ + + BOOST_CHECK(boost::filesystem::is_symlink(find_prefix(out, "j2c_"))); + BOOST_CHECK(boost::filesystem::is_symlink(find_prefix(out, "pcm_"))); +} + + +/** Map a single DCP into a new DCP using the hardlink option */ +BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_hardlinks) +{ + string const name = "map_simple_dcp_copy_with_hardlinks"; + string const out = String::compose("build/test/%1_out", name); + + auto content = content_factory("test/data/flat_red.png"); + auto film = new_test_film2(name + "_in", content); + make_and_verify_dcp(film); + + vector const args = { + "map_cli", + "-o", out, + "-d", film->dir(film->dcp_name()).string(), + "-l", + find_cpl(film->dir(film->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, {}); + + /* The video file will have 3 links because DoM also makes a link into the video directory */ + BOOST_CHECK_EQUAL(boost::filesystem::hard_link_count(find_prefix(out, "j2c_")), 3U); + BOOST_CHECK_EQUAL(boost::filesystem::hard_link_count(find_prefix(out, "pcm_")), 2U); +} + + +/** Map a single Interop DCP with subs into a new DCP */ +BOOST_AUTO_TEST_CASE(map_simple_interop_dcp_with_subs) +{ + string const name = "map_simple_interop_dcp_with_subs"; + string const out = String::compose("build/test/%1_out", name); + + auto picture = content_factory("test/data/flat_red.png").front(); + auto subs = content_factory("test/data/15s.srt").front(); + auto film = new_test_film2(name + "_in", { picture, subs }); + film->set_interop(true); + make_and_verify_dcp(film, {dcp::VerificationNote::Code::INVALID_STANDARD}); + + vector const args = { + "map_cli", + "-o", out, + "-d", film->dir(film->dcp_name()).string(), + find_cpl(film->dir(film->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, {dcp::VerificationNote::Code::INVALID_STANDARD}); +} + + +/** Map an OV and a VF into a single DCP */ +BOOST_AUTO_TEST_CASE(map_ov_vf_copy) +{ + string const name = "map_ov_vf_copy"; + string const out = String::compose("build/test/%1_out", name); + + auto ov_content = content_factory("test/data/flat_red.png"); + auto ov_film = new_test_film2(name + "_ov", ov_content); + make_and_verify_dcp(ov_film); + + auto const ov_dir = ov_film->dir(ov_film->dcp_name()); + + auto vf_ov = make_shared(ov_dir); + auto vf_sound = content_factory("test/data/sine_440.wav").front(); + auto vf_film = new_test_film2(name + "_vf", { vf_ov, vf_sound }); + vf_ov->set_reference_video(true); + make_and_verify_dcp(vf_film, {dcp::VerificationNote::Code::EXTERNAL_ASSET}); + + auto const vf_dir = vf_film->dir(vf_film->dcp_name()); + + vector const args = { + "map_cli", + "-o", out, + "-d", ov_dir.string(), + "-d", vf_dir.string(), + find_cpl(vf_dir).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, {}); + + check_file(find_file(out, "cpl_"), find_file(vf_dir, "cpl_")); + check_file(find_file(out, "j2c_"), find_file(ov_dir, "j2c_")); + check_file(find_file(out, "pcm_"), find_file(vf_dir, "pcm_")); +} + + +/** Map a single DCP into a new DCP using the rename option */ +BOOST_AUTO_TEST_CASE(map_simple_dcp_copy_with_rename) +{ + ConfigRestorer cr; + Config::instance()->set_dcp_asset_filename_format(dcp::NameFormat("hello%c")); + string const name = "map_simple_dcp_copy_with_rename"; + string const out = String::compose("build/test/%1_out", name); + + auto content = content_factory("test/data/flat_red.png"); + auto film = new_test_film2(name + "_in", content); + make_and_verify_dcp(film); + + vector const args = { + "map_cli", + "-o", out, + "-d", film->dir(film->dcp_name()).string(), + "-r", + find_cpl(film->dir(film->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, {}); + + dcp::DCP out_dcp(out); + out_dcp.read(); + + BOOST_REQUIRE_EQUAL(out_dcp.cpls().size(), 1U); + auto const cpl = out_dcp.cpls()[0]; + BOOST_REQUIRE_EQUAL(cpl->reels().size(), 1U); + auto const reel = cpl->reels()[0]; + BOOST_REQUIRE(reel->main_picture()); + BOOST_REQUIRE(reel->main_sound()); + auto const picture = reel->main_picture()->asset(); + BOOST_REQUIRE(picture); + auto const sound = reel->main_sound()->asset(); + BOOST_REQUIRE(sound); + + BOOST_REQUIRE(picture->file()); + BOOST_CHECK(picture->file().get().filename() == picture->id() + ".mxf"); + + BOOST_REQUIRE(sound->file()); + BOOST_CHECK(sound->file().get().filename() == sound->id() + ".mxf"); +} + + +static +void +test_two_cpls_each_with_subs(string name, bool interop) +{ + string const out = String::compose("build/test/%1_out", name); + + vector acceptable_errors; + if (interop) { + acceptable_errors.push_back(dcp::VerificationNote::Code::INVALID_STANDARD); + } else { + acceptable_errors.push_back(dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE); + acceptable_errors.push_back(dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME); + } + + shared_ptr films[2]; + for (auto i = 0; i < 2; ++i) { + auto picture = content_factory("test/data/flat_red.png").front(); + auto subs = content_factory("test/data/15s.srt").front(); + films[i] = new_test_film2(String::compose("%1_%2_in", name, i), { picture, subs }); + films[i]->set_interop(interop); + make_and_verify_dcp(films[i], acceptable_errors); + } + + vector const args = { + "map_cli", + "-o", out, + "-d", films[0]->dir(films[0]->dcp_name()).string(), + "-d", films[1]->dir(films[1]->dcp_name()).string(), + find_cpl(films[0]->dir(films[0]->dcp_name())).string(), + find_cpl(films[1]->dir(films[1]->dcp_name())).string() + }; + + boost::filesystem::remove_all(out); + + vector output_messages; + auto error = run(args, output_messages); + BOOST_CHECK(!error); + + verify_dcp(out, acceptable_errors); +} + + +BOOST_AUTO_TEST_CASE(map_two_interop_cpls_each_with_subs) +{ + test_two_cpls_each_with_subs("map_two_interop_cpls_each_with_subs", true); +} + + +BOOST_AUTO_TEST_CASE(map_two_smpte_cpls_each_with_subs) +{ + test_two_cpls_each_with_subs("map_two_smpte_cpls_each_with_subs", false); +} diff --git a/test/test.cc b/test/test.cc index 354a79e26..06ec23dbc 100644 --- a/test/test.cc +++ b/test/test.cc @@ -132,6 +132,7 @@ setup_test_config () auto decryption = make_shared(dcp::file_to_string("test/data/decryption_chain")); decryption->set_key(dcp::file_to_string("test/data/decryption_key")); Config::instance()->set_decryption_chain (decryption); + Config::instance()->set_dcp_asset_filename_format(dcp::NameFormat("%t")); } diff --git a/test/wscript b/test/wscript index 827a3feb8..88cee9b07 100644 --- a/test/wscript +++ b/test/wscript @@ -113,6 +113,7 @@ def build(bld): kdm_util_test.cc low_bitrate_test.cc markers_test.cc + map_cli_test.cc mca_subdescriptors_test.cc no_use_video_test.cc optimise_stills_test.cc