From: Carl Hetherington Date: Wed, 1 Mar 2023 00:26:49 +0000 (+0100) Subject: Add option to limit DCP output to the "Bv2.0 profile" (#2470). X-Git-Tag: v2.16.45 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=34b2b0fe412332505e3d543358c9741bd068602d Add option to limit DCP output to the "Bv2.0 profile" (#2470). I'm far from convinced about the point/sense of all these "profiles" (rather than just implementing or at least tolerating the standard) but lots of people are having problems with "QC" processes failing their DCPs with complaints related to MCASubDescriptors. It seems to make sense to have an option to turn them off - at least for now, until either the "QC" situation settles down or any bugs in DCP-o-matic are found and fixed. --- diff --git a/cscript b/cscript index 4b2843a8b..87c42883c 100644 --- a/cscript +++ b/cscript @@ -457,7 +457,7 @@ def dependencies(target, options): # Use distro-provided FFmpeg on Arch deps = [] - deps.append(('libdcp', 'v1.8.63')) + deps.append(('libdcp', 'v1.8.64')) deps.append(('libsub', 'v1.6.43')) deps.append(('leqm-nrt', '4560105773c66ac9216b62313a24093bb0a027ae')) deps.append(('rtaudio', 'f619b76')) diff --git a/src/lib/config.cc b/src/lib/config.cc index 2db50d687..908a438e9 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -198,6 +198,7 @@ Config::set_defaults () _default_kdm_duration = RoughDuration(1, RoughDuration::Unit::WEEKS); _auto_crop_threshold = 0.1; _last_release_notes_version = boost::none; + _allow_smpte_bv20 = false; _allowed_dcp_frame_rates.clear (); _allowed_dcp_frame_rates.push_back (24); @@ -629,6 +630,8 @@ try } } + _allow_smpte_bv20 = f.optional_bool_child("AllowSMPTEBv20").get_value_or(false); + _export.read(f.optional_node_child("Export")); } catch (...) { @@ -1110,6 +1113,9 @@ Config::write_config () const _default_add_file_location == DefaultAddFileLocation::SAME_AS_LAST_TIME ? "last" : "project" ); + /* [XML] AllowSMPTEBv20 1 to allow the user to choose SMPTE (Bv2.0 only) as a standard, otherwise 0 */ + root->add_child("AllowSMPTEBv20")->add_child_text(_allow_smpte_bv20 ? "1" : "0"); + _export.write(root->add_child("Export")); auto target = config_write_file(); diff --git a/src/lib/config.h b/src/lib/config.h index a816cd89b..91d779b7a 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -93,6 +93,7 @@ public: SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, AUDIO_MAPPING, AUTO_CROP_THRESHOLD, + ALLOW_SMPTE_BV20, OTHER }; @@ -612,6 +613,10 @@ public: return _default_add_file_location; } + bool allow_smpte_bv20() const { + return _allow_smpte_bv20; + } + /* SET (mostly) */ void set_master_encoding_threads (int n) { @@ -1185,6 +1190,10 @@ public: maybe_set(_default_add_file_location, location); } + void set_allow_smpte_bv20(bool allow) { + maybe_set(_allow_smpte_bv20, allow, ALLOW_SMPTE_BV20); + } + void changed (Property p = OTHER); boost::signals2::signal Changed; /** Emitted if read() failed on an existing Config file. There is nothing @@ -1422,6 +1431,7 @@ private: boost::optional _main_divider_sash_position; boost::optional _main_content_divider_sash_position; DefaultAddFileLocation _default_add_file_location; + bool _allow_smpte_bv20; ExportConfig _export; diff --git a/src/lib/film.cc b/src/lib/film.cc index 69d55c7c4..25a135488 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -166,6 +166,7 @@ Film::Film (optional dir) , _three_d (false) , _sequence (true) , _interop (Config::instance()->default_interop ()) + , _limit_to_smpte_bv20(false) , _audio_processor (0) , _reel_type (ReelType::SINGLE) , _reel_length (2000000000) @@ -269,6 +270,11 @@ Film::video_identifier () const s += "_I"; } else { s += "_S"; + if (_limit_to_smpte_bv20) { + s += "_L20"; + } else { + s += "_L21"; + } } if (_three_d) { @@ -416,6 +422,7 @@ Film::metadata (bool with_content_paths) const root->add_child("ThreeD")->add_child_text (_three_d ? "1" : "0"); root->add_child("Sequence")->add_child_text (_sequence ? "1" : "0"); root->add_child("Interop")->add_child_text (_interop ? "1" : "0"); + root->add_child("LimitToSMPTEBv20")->add_child_text(_limit_to_smpte_bv20 ? "1" : "0"); root->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0"); root->add_child("Key")->add_child_text (_key.hex ()); root->add_child("ContextID")->add_child_text (_context_id); @@ -586,6 +593,7 @@ Film::read_metadata (optional path) _three_d = f.bool_child ("ThreeD"); _interop = f.bool_child ("Interop"); + _limit_to_smpte_bv20 = f.optional_bool_child("LimitToSMPTEBv20").get_value_or(false); _key = dcp::Key (f.string_child ("Key")); _context_id = f.optional_string_child("ContextID").get_value_or (dcp::make_uuid ()); @@ -1179,6 +1187,15 @@ Film::set_interop (bool i) _interop = i; } + +void +Film::set_limit_to_smpte_bv20(bool limit) +{ + FilmChangeSignaller ch(this, Property::LIMIT_TO_SMPTE_BV20); + _limit_to_smpte_bv20 = limit; +} + + void Film::set_audio_processor (AudioProcessor const * processor) { diff --git a/src/lib/film.h b/src/lib/film.h index 7ae22052a..b7a9f94ac 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -228,6 +228,7 @@ public: THREE_D, SEQUENCE, INTEROP, + LIMIT_TO_SMPTE_BV20, AUDIO_PROCESSOR, REEL_TYPE, REEL_LENGTH, @@ -312,6 +313,10 @@ public: return _interop; } + bool limit_to_smpte_bv20() const { + return _limit_to_smpte_bv20; + } + AudioProcessor const * audio_processor () const { return _audio_processor; } @@ -433,6 +438,7 @@ public: void set_isdcf_date_today (); void set_sequence (bool); void set_interop (bool); + void set_limit_to_smpte_bv20(bool); void set_audio_processor (AudioProcessor const * processor); void set_reel_type (ReelType); void set_reel_length (int64_t); @@ -544,6 +550,7 @@ private: bool _three_d; bool _sequence; bool _interop; + bool _limit_to_smpte_bv20; AudioProcessor const * _audio_processor; ReelType _reel_type; /** Desired reel length in bytes, if _reel_type == REELTYPE_BY_LENGTH */ diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc index 47df4feb1..31860e881 100644 --- a/src/lib/reel_writer.cc +++ b/src/lib/reel_writer.cc @@ -195,7 +195,8 @@ ReelWriter::ReelWriter ( */ _sound_asset_writer = _sound_asset->start_write ( film()->directory().get() / audio_asset_filename (_sound_asset, _reel_index, _reel_count, _content_summary), - film()->contains_atmos_content() + film()->contains_atmos_content(), + !film()->limit_to_smpte_bv20() ); } diff --git a/src/lib/writer.cc b/src/lib/writer.cc index a36944723..1c8f1a0cd 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -691,7 +691,7 @@ Writer::finish (boost::filesystem::path output_dcp) dcp.set_creator(creator); dcp.set_annotation_text(film()->dcp_name()); - dcp.write_xml (signer, Config::instance()->dcp_metadata_filename_format()); + dcp.write_xml(signer, !film()->limit_to_smpte_bv20(), Config::instance()->dcp_metadata_filename_format()); LOG_GENERAL ( N_("Wrote %1 FULL, %2 FAKE, %3 REPEAT, %4 pushed to disk"), _full_written, _fake_written, _repeat_written, _pushed_to_disk diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc index 19f26d9d0..3e1210543 100644 --- a/src/wx/dcp_panel.cc +++ b/src/wx/dcp_panel.cc @@ -154,8 +154,7 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr film, FilmViewer& viewer) _reel_length->SetRange (1, 64); - _standard->add(_("SMPTE")); - _standard->add(_("Interop")); + add_standards(); _standard->SetToolTip(_("Which standard the DCP should use. Interop is older and SMPTE is the modern standard. If in doubt, choose 'SMPTE'")); Config::instance()->Changed.connect (boost::bind(&DCPPanel::config_changed, this, _1)); @@ -163,6 +162,57 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr film, FilmViewer& viewer) add_to_grid (); } + +void +DCPPanel::add_standards() +{ + _standard->add(_("SMPTE"), N_("smpte")); + if (Config::instance()->allow_smpte_bv20() || (_film && _film->limit_to_smpte_bv20())) { + _standard->add(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20")); + } + _standard->add(_("Interop"), N_("interop")); +} + + +void +DCPPanel::set_standard() +{ + DCPOMATIC_ASSERT(_film); + DCPOMATIC_ASSERT(!_film->limit_to_smpte_bv20() || _standard->GetCount() == 3); + + if (_film->interop()) { + checked_set(_standard, "interop"); + } else { + checked_set(_standard, _film->limit_to_smpte_bv20() ? "smpte-bv20" : "smpte"); + } +} + + +void +DCPPanel::standard_changed () +{ + if (!_film || !_standard->get()) { + return; + } + + auto const data = _standard->get_data(); + if (!data) { + return; + } + + if (*data == N_("interop")) { + _film->set_interop(true); + _film->set_limit_to_smpte_bv20(false); + } else if (*data == N_("smpte")) { + _film->set_interop(false); + _film->set_limit_to_smpte_bv20(false); + } else if (*data == N_("smpte-bv20")) { + _film->set_interop(false); + _film->set_limit_to_smpte_bv20(true); + } +} + + void DCPPanel::add_to_grid () { @@ -315,17 +365,6 @@ DCPPanel::resolution_changed () } -void -DCPPanel::standard_changed () -{ - if (!_film || !_standard->get()) { - return; - } - - _film->set_interop(*_standard->get() == 1); - -} - void DCPPanel::markers_clicked () { @@ -434,10 +473,13 @@ DCPPanel::film_changed (Film::Property p) checked_set (_reencode_j2k, _film->reencode_j2k()); break; case Film::Property::INTEROP: - checked_set (_standard, _film->interop() ? 1 : 0); + set_standard(); setup_dcp_name (); _markers->Enable (!_film->interop()); break; + case Film::Property::LIMIT_TO_SMPTE_BV20: + set_standard(); + break; case Film::Property::AUDIO_PROCESSOR: if (_film->audio_processor()) { checked_set (_audio_processor, _film->audio_processor()->id()); @@ -587,6 +629,9 @@ DCPPanel::set_film (shared_ptr film) return; } + _standard->Clear(); + add_standards(); + film_changed (Film::Property::NAME); film_changed (Film::Property::USE_ISDCF_NAME); film_changed (Film::Property::CONTENT); @@ -606,6 +651,7 @@ DCPPanel::set_film (shared_ptr film) film_changed (Film::Property::REENCODE_J2K); film_changed (Film::Property::AUDIO_LANGUAGE); film_changed (Film::Property::AUDIO_FRAME_RATE); + film_changed (Film::Property::LIMIT_TO_SMPTE_BV20); set_general_sensitivity(static_cast(_film)); } @@ -726,6 +772,13 @@ DCPPanel::config_changed (Config::Property p) if (_film) { film_changed (Film::Property::AUDIO_PROCESSOR); } + } else if (p == Config::ALLOW_SMPTE_BV20) { + _standard->Clear(); + add_standards(); + if (_film) { + film_changed(Film::Property::INTEROP); + film_changed(Film::Property::LIMIT_TO_SMPTE_BV20); + } } } diff --git a/src/wx/dcp_panel.h b/src/wx/dcp_panel.h index cd39f2d1e..6635d4a29 100644 --- a/src/wx/dcp_panel.h +++ b/src/wx/dcp_panel.h @@ -98,6 +98,8 @@ private: void add_video_panel_to_grid (); void add_audio_panel_to_grid (); void add_audio_processors (); + void add_standards(); + void set_standard(); int minimum_allowed_audio_channels () const; diff --git a/src/wx/full_config_dialog.cc b/src/wx/full_config_dialog.cc index 59c1d4c9f..ec098ad32 100644 --- a/src/wx/full_config_dialog.cc +++ b/src/wx/full_config_dialog.cc @@ -1544,6 +1544,7 @@ private: checkbox(_("Allow creation of DCPs with 96kHz audio"), _allow_96khz_audio); checkbox(_("Allow mapping to all audio channels"), _use_all_audio_channels); + checkbox(_("Allow use of SMPTE Bv2.0"), _allow_smpte_bv20); _maximum_j2k_bandwidth->SetRange(1, 1000); _maximum_j2k_bandwidth->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_bandwidth_changed, this)); @@ -1551,6 +1552,7 @@ private: _allow_any_container->bind(&NonStandardPage::allow_any_container_changed, this); _allow_96khz_audio->bind(&NonStandardPage::allow_96khz_audio_changed, this); _use_all_audio_channels->bind(&NonStandardPage::use_all_channels_changed, this); + _allow_smpte_bv20->bind(&NonStandardPage::allow_smpte_bv20_changed, this); } void config_changed() override @@ -1562,6 +1564,7 @@ private: checked_set(_allow_any_container, config->allow_any_container()); checked_set(_allow_96khz_audio, config->allow_96khz_audio()); checked_set(_use_all_audio_channels, config->use_all_audio_channels()); + checked_set(_allow_smpte_bv20, config->allow_smpte_bv20()); } void maximum_j2k_bandwidth_changed() @@ -1589,11 +1592,17 @@ private: Config::instance()->set_use_all_audio_channels(_use_all_audio_channels->GetValue()); } + void allow_smpte_bv20_changed() + { + Config::instance()->set_allow_smpte_bv20(_allow_smpte_bv20->GetValue()); + } + wxSpinCtrl* _maximum_j2k_bandwidth = nullptr; CheckBox* _allow_any_dcp_frame_rate = nullptr; CheckBox* _allow_any_container = nullptr; CheckBox* _allow_96khz_audio = nullptr; CheckBox* _use_all_audio_channels = nullptr; + CheckBox* _allow_smpte_bv20 = nullptr; }; diff --git a/test/bv20_test.cc b/test/bv20_test.cc new file mode 100644 index 000000000..5530a05d0 --- /dev/null +++ b/test/bv20_test.cc @@ -0,0 +1,95 @@ +/* + 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/content_factory.h" +#include "lib/film.h" +#include "test.h" +#include +LIBDCP_DISABLE_WARNINGS +#include +LIBDCP_ENABLE_WARNINGS +#include +#include +#include + + +using std::shared_ptr; + + +bool +has_cpl_mca_subdescriptors(shared_ptr film) +{ + auto cpl = dcp::file_to_string(find_file(film->dir(film->dcp_name()), "cpl_")); + return cpl.find("MCASubDescriptors") != std::string::npos; +} + + +bool +has_mxf_mca_subdescriptors(shared_ptr film) +{ + /* One day hopefully libdcp will read these descriptors and we can find out from the SoundAsset + * whether they exist. + */ + + ASDCP::PCM::MXFReader reader; + auto r = reader.OpenRead(find_file(film->dir(film->dcp_name()), "pcm_").string()); + BOOST_REQUIRE(!ASDCP_FAILURE(r)); + + ASDCP::MXF::WaveAudioDescriptor* essence_descriptor = nullptr; + auto const rr = reader.OP1aHeader().GetMDObjectByType( + dcp::asdcp_smpte_dict->ul(ASDCP::MDD_WaveAudioDescriptor), + reinterpret_cast(&essence_descriptor) + ); + + if (!KM_SUCCESS(rr)) { + return false; + } + + return essence_descriptor->SubDescriptors.size() > 0; +} + + +BOOST_AUTO_TEST_CASE(bv21_extensions_used_when_not_limited) +{ + auto picture = content_factory("test/data/flat_red.png"); + auto sound = content_factory("test/data/sine_440.wav"); + auto film = new_test_film2("bv21_extensions_used_when_not_limited", { picture.front(), sound.front() }); + + make_and_verify_dcp(film); + + BOOST_CHECK(has_cpl_mca_subdescriptors(film)); + BOOST_CHECK(has_mxf_mca_subdescriptors(film)); +} + + +BOOST_AUTO_TEST_CASE(bv21_extensions_not_used_when_limited) +{ + auto picture = content_factory("test/data/flat_red.png"); + auto sound = content_factory("test/data/sine_440.wav"); + auto film = new_test_film2("bv21_extensions_not_used_when_limited", { picture.front(), sound.front () }); + film->set_limit_to_smpte_bv20(true); + + make_and_verify_dcp(film); + + BOOST_CHECK(!has_cpl_mca_subdescriptors(film)); + BOOST_CHECK(!has_mxf_mca_subdescriptors(film)); +} + diff --git a/test/data b/test/data index aede894d8..1bda4d6e0 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit aede894d8bf60f18915bf6f7046af05d9bfe585e +Subproject commit 1bda4d6e0a3fd9c455166aa9a6b70ac348bc83d0 diff --git a/test/recover_test.cc b/test/recover_test.cc index 0288da188..9508adca7 100644 --- a/test/recover_test.cc +++ b/test/recover_test.cc @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE (recover_test_2d) dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE }); - boost::filesystem::path const video = "build/test/recover_test_2d/video/185_2K_02543352c540f4b083bff3f1e309d4a9_24_100000000_P_S_0_1200000.mxf"; + boost::filesystem::path const video = "build/test/recover_test_2d/video/185_2K_02543352c540f4b083bff3f1e309d4a9_24_100000000_P_S_L21_0_1200000.mxf"; boost::filesystem::copy_file ( video, "build/test/recover_test_2d/original.mxf" @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE (recover_test_3d, * boost::unit_test::depends_on("recover_t make_and_verify_dcp (film, { dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }); - boost::filesystem::path const video = "build/test/recover_test_3d/video/185_2K_70e6661af92ae94458784c16a21a9748_24_100000000_P_S_3D_0_96000.mxf"; + boost::filesystem::path const video = "build/test/recover_test_3d/video/185_2K_70e6661af92ae94458784c16a21a9748_24_100000000_P_S_L21_3D_0_96000.mxf"; boost::filesystem::copy_file ( video, @@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE (recover_test_2d_encrypted, * boost::unit_test::depends_on( make_and_verify_dcp (film, { dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE, dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE }); boost::filesystem::path const video = - "build/test/recover_test_2d_encrypted/video/185_2K_02543352c540f4b083bff3f1e309d4a9_24_100000000_Eeafcb91c9f5472edf01f3a2404c57258_S_0_1200000.mxf"; + "build/test/recover_test_2d_encrypted/video/185_2K_02543352c540f4b083bff3f1e309d4a9_24_100000000_Eeafcb91c9f5472edf01f3a2404c57258_S_L21_0_1200000.mxf"; boost::filesystem::copy_file ( video, diff --git a/test/wscript b/test/wscript index 949f69019..6c85def54 100644 --- a/test/wscript +++ b/test/wscript @@ -57,6 +57,7 @@ def build(bld): audio_processor_delay_test.cc audio_ring_buffers_test.cc butler_test.cc + bv20_test.cc cinema_sound_processor_test.cc client_server_test.cc closed_caption_test.cc