Add option to limit DCP output to the "Bv2.0 profile" (#2470). v2.16.45
authorCarl Hetherington <cth@carlh.net>
Wed, 1 Mar 2023 00:26:49 +0000 (01:26 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 3 Mar 2023 00:49:55 +0000 (01:49 +0100)
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.

14 files changed:
cscript
src/lib/config.cc
src/lib/config.h
src/lib/film.cc
src/lib/film.h
src/lib/reel_writer.cc
src/lib/writer.cc
src/wx/dcp_panel.cc
src/wx/dcp_panel.h
src/wx/full_config_dialog.cc
test/bv20_test.cc [new file with mode: 0644]
test/data
test/recover_test.cc
test/wscript

diff --git a/cscript b/cscript
index 4b2843a8b5321b9aff6b43343f33af33c9b86dbf..87c42883c81713acd37957ddc1c3221fcb8d63b1 100644 (file)
--- 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'))
index 2db50d687a26aa94f2890b739310d44b43460020..908a438e93c0e0c9b4c89cd9dd4651c8bcfa8ada 100644 (file)
@@ -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();
index a816cd89bc99a03f7e6e426cce93b43dd3823e65..91d779b7ad7b0bcca7b6c06c25542c9fcec408d0 100644 (file)
@@ -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<void (Property)> Changed;
        /** Emitted if read() failed on an existing Config file.  There is nothing
@@ -1422,6 +1431,7 @@ private:
        boost::optional<int> _main_divider_sash_position;
        boost::optional<int> _main_content_divider_sash_position;
        DefaultAddFileLocation _default_add_file_location;
+       bool _allow_smpte_bv20;
 
        ExportConfig _export;
 
index 69d55c7c43853ad1d7371645b6213043eb929ec9..25a135488f2c9981ea623b5345ded1552ed04d0e 100644 (file)
@@ -166,6 +166,7 @@ Film::Film (optional<boost::filesystem::path> 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<boost::filesystem::path> 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)
 {
index 7ae22052ae6a413e15bfef3540380e98a454ab5c..b7a9f94ace3a03c62ab69102684672703aae1135 100644 (file)
@@ -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 */
index 47df4feb159ac458dc2556df203d3e9edfdc2b6e..31860e8816131da9973231612a19d415866001d9 100644 (file)
@@ -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()
                        );
        }
 
index a369447232f5aa99aa03d70a10fa5232d090833b..1c8f1a0cd3d8ec2fa3f830b0d57487dd0c0a00ba 100644 (file)
@@ -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
index 19f26d9d0af0c75f3a645a7930731a12ad02f3c0..3e121054326473fd4017285f1cb23868cfcef1cc 100644 (file)
@@ -154,8 +154,7 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> 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> 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> 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)
        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<bool>(_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);
+               }
        }
 }
 
index cd39f2d1ef11b5de46cbbea78b9524a50fb76e64..6635d4a2980ab5066ba85fe53550fbe399dc817a 100644 (file)
@@ -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;
 
index 59c1d4c9ffdae06ef3dae5ef70671b4827952ec6..ec098ad3226d4cc0e687e2bdc181c5ba95f99654 100644 (file)
@@ -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 (file)
index 0000000..5530a05
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+    Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+    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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/content_factory.h"
+#include "lib/film.h"
+#include "test.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <asdcp/Metadata.h>
+LIBDCP_ENABLE_WARNINGS
+#include <dcp/sound_asset.h>
+#include <dcp/util.h>
+#include <boost/test/unit_test.hpp>
+
+
+using std::shared_ptr;
+
+
+bool
+has_cpl_mca_subdescriptors(shared_ptr<const Film> 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<const Film> 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<ASDCP::MXF::InterchangeObject**>(&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));
+}
+
index aede894d8bf60f18915bf6f7046af05d9bfe585e..1bda4d6e0a3fd9c455166aa9a6b70ac348bc83d0 160000 (submodule)
--- a/test/data
+++ b/test/data
@@ -1 +1 @@
-Subproject commit aede894d8bf60f18915bf6f7046af05d9bfe585e
+Subproject commit 1bda4d6e0a3fd9c455166aa9a6b70ac348bc83d0
index 0288da1883e1c180c87fe2ab953a67f634b4dab9..9508adca7ef24ef728b5e0b2d98ae01f7d11ed84 100644 (file)
@@ -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,
index 949f69019eb458a2011053a3d82b54491935316e..6c85def54bddc4037751b609fd7cc2a9eec661ea 100644 (file)
@@ -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