summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-10-31 00:03:49 +0100
committerCarl Hetherington <cth@carlh.net>2025-11-05 00:43:19 +0100
commite8ce097ce705446c27b51199a321a9918deaa0db (patch)
tree0a52cbc9c52791d453fa4bd478609aedde98c831
parentef65a179e8c907029d0d9254863d4884581f3d60 (diff)
Allow specification of which parts of the DCP to encrypt (#3099).
-rw-r--r--src/lib/copy_dcp_details_to_film.cc4
-rw-r--r--src/lib/create_cli.cc8
-rw-r--r--src/lib/dcp_content.cc20
-rw-r--r--src/lib/dcp_content.h25
-rw-r--r--src/lib/dcp_examiner.cc18
-rw-r--r--src/lib/dcp_examiner.h16
-rw-r--r--src/lib/film.cc44
-rw-r--r--src/lib/film.h25
-rw-r--r--src/lib/film_property.h4
-rw-r--r--src/lib/reel_writer.cc8
-rw-r--r--src/lib/writer.cc2
-rw-r--r--src/wx/dcp_panel.cc34
-rw-r--r--src/wx/dcp_panel.h6
-rw-r--r--src/wx/encryption_settings_dialog.cc64
-rw-r--r--src/wx/encryption_settings_dialog.h43
-rw-r--r--src/wx/wscript1
-rw-r--r--test/atmos_test.cc4
m---------test/data0
-rw-r--r--test/dcp_decoder_test.cc4
-rw-r--r--test/dcp_digest_file_test.cc6
-rw-r--r--test/encryption_test.cc106
-rw-r--r--test/import_dcp_test.cc3
-rw-r--r--test/kdm_cli_test.cc4
-rw-r--r--test/kdm_naming_test.cc6
-rw-r--r--test/player_test.cc4
-rw-r--r--test/recover_test.cc3
-rw-r--r--test/remake_id_test.cc3
-rw-r--r--test/vf_kdm_test.cc6
28 files changed, 419 insertions, 52 deletions
diff --git a/src/lib/copy_dcp_details_to_film.cc b/src/lib/copy_dcp_details_to_film.cc
index 9e0ad79c1..67a207de1 100644
--- a/src/lib/copy_dcp_details_to_film.cc
+++ b/src/lib/copy_dcp_details_to_film.cc
@@ -45,7 +45,9 @@ copy_dcp_settings_to_film(shared_ptr<const DCPContent> dcp, shared_ptr<Film> fil
if (dcp->content_kind()) {
film->set_dcp_content_type(DCPContentType::from_libdcp_kind(dcp->content_kind().get()));
}
- film->set_encrypted(dcp->encrypted());
+ film->set_encrypt_picture(dcp->picture_encrypted());
+ film->set_encrypt_sound(dcp->sound_encrypted());
+ film->set_encrypt_text(dcp->text_encrypted());
film->set_reel_type(ReelType::BY_VIDEO_CONTENT);
film->set_interop(dcp->standard() == dcp::Standard::INTEROP);
film->set_three_d(dcp->three_d());
diff --git a/src/lib/create_cli.cc b/src/lib/create_cli.cc
index af2e90745..32834be23 100644
--- a/src/lib/create_cli.cc
+++ b/src/lib/create_cli.cc
@@ -473,9 +473,13 @@ CreateCLI::make_film(function<void (string)> error) const
}
film->set_use_isdcf_name(!_no_use_isdcf_name);
if (_no_encrypt) {
- film->set_encrypted(false);
+ film->set_encrypt_picture(false);
+ film->set_encrypt_sound(false);
+ film->set_encrypt_text(false);
} else if (_encrypt) {
- film->set_encrypted(true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(true);
}
if (_twod) {
film->set_three_d(false);
diff --git a/src/lib/dcp_content.cc b/src/lib/dcp_content.cc
index cff14deca..c75babfc6 100644
--- a/src/lib/dcp_content.cc
+++ b/src/lib/dcp_content.cc
@@ -66,7 +66,9 @@ using namespace dcpomatic;
DCPContent::DCPContent(boost::filesystem::path p)
- : _encrypted(false)
+ : _picture_encrypted(false)
+ , _sound_encrypted(false)
+ , _text_encrypted(false)
, _needs_assets(false)
, _kdm_valid(false)
, _reference_video(false)
@@ -103,7 +105,10 @@ DCPContent::DCPContent(cxml::ConstNodePtr node, boost::optional<boost::filesyste
}
_name = node->string_child("Name");
- _encrypted = node->bool_child("Encrypted");
+ auto encrypted = node->optional_bool_child("Encrypted").get_value_or(false);
+ _picture_encrypted = node->optional_bool_child("PictureEncrypted").get_value_or(encrypted);
+ _sound_encrypted = node->optional_bool_child("SoundEncrypted").get_value_or(encrypted);
+ _text_encrypted = node->optional_bool_child("TextEncrypted").get_value_or(encrypted);
_needs_assets = node->optional_bool_child("NeedsAssets").get_value_or(false);
if (node->optional_node_child("KDM")) {
_kdm = dcp::EncryptedKDM(node->string_child("KDM"));
@@ -314,7 +319,9 @@ DCPContent::examine(shared_ptr<const Film> film, shared_ptr<Job> job, bool toler
boost::mutex::scoped_lock lm(_mutex);
text = new_text;
_name = examiner->name();
- _encrypted = examiner->encrypted();
+ _picture_encrypted = examiner->picture_encrypted();
+ _sound_encrypted = examiner->sound_encrypted();
+ _text_encrypted = examiner->text_encrypted();
_needs_assets = examiner->needs_assets();
_kdm_valid = examiner->kdm_valid();
_standard = examiner->standard();
@@ -396,7 +403,10 @@ DCPContent::as_xml(xmlpp::Element* element, bool with_paths, PathBehaviour path_
boost::mutex::scoped_lock lm(_mutex);
cxml::add_text_child(element, "Name", _name);
- cxml::add_text_child(element, "Encrypted", _encrypted ? "1" : "0");
+ cxml::add_text_child(element, "Encrypted", (_picture_encrypted || _sound_encrypted || _text_encrypted) ? "1" : "0");
+ cxml::add_text_child(element, "PictureEncrypted", _picture_encrypted ? "1" : "0");
+ cxml::add_text_child(element, "SoundEncrypted", _sound_encrypted ? "1" : "0");
+ cxml::add_text_child(element, "TextEncrypted", _text_encrypted ? "1" : "0");
cxml::add_text_child(element, "NeedsAssets", _needs_assets ? "1" : "0");
if (_kdm) {
cxml::add_text_child(element, "KDM", _kdm->as_xml());
@@ -526,7 +536,7 @@ bool
DCPContent::needs_kdm() const
{
boost::mutex::scoped_lock lm(_mutex);
- return _encrypted && !_kdm_valid;
+ return (_picture_encrypted || _sound_encrypted || _text_encrypted) && !_kdm_valid;
}
bool
diff --git a/src/lib/dcp_content.h b/src/lib/dcp_content.h
index 1b58c8efc..97e4b3cc4 100644
--- a/src/lib/dcp_content.h
+++ b/src/lib/dcp_content.h
@@ -97,7 +97,25 @@ public:
bool encrypted() const {
boost::mutex::scoped_lock lm(_mutex);
- return _encrypted;
+ return _picture_encrypted || _sound_encrypted || _text_encrypted;
+ }
+
+ /** @return true if any picture asset in this DCP is encrypted */
+ bool picture_encrypted() const {
+ boost::mutex::scoped_lock lm(_mutex);
+ return _picture_encrypted;
+ }
+
+ /** @return true if any sound asset in this DCP is encrypted */
+ bool sound_encrypted() const {
+ boost::mutex::scoped_lock lm(_mutex);
+ return _sound_encrypted;
+ }
+
+ /** @return true if any text asset in this DCP is encrypted */
+ bool text_encrypted() const {
+ boost::mutex::scoped_lock lm(_mutex);
+ return _text_encrypted;
}
void add_kdm(dcp::EncryptedKDM);
@@ -217,8 +235,9 @@ private:
bool overlaps(std::shared_ptr<const Film> film, std::function<bool (std::shared_ptr<const Content>)> part) const;
std::string _name;
- /** true if our DCP is encrypted */
- bool _encrypted;
+ bool _picture_encrypted;
+ bool _sound_encrypted;
+ bool _text_encrypted;
/** true if this DCP needs more assets before it can be played */
bool _needs_assets;
boost::optional<dcp::EncryptedKDM> _kdm;
diff --git a/src/lib/dcp_examiner.cc b/src/lib/dcp_examiner.cc
index 59bd47702..b94d88486 100644
--- a/src/lib/dcp_examiner.cc
+++ b/src/lib/dcp_examiner.cc
@@ -313,7 +313,23 @@ DCPExaminer::DCPExaminer(shared_ptr<const DCPContent> content, bool tolerant)
++reel_index;
}
- _encrypted = selected_cpl->any_encrypted();
+ for (auto reel: selected_cpl->reels()) {
+ if (reel->main_picture() && reel->main_picture()->encrypted()) {
+ _picture_encrypted = true;
+ }
+ if (reel->main_sound() && reel->main_sound()->encrypted()) {
+ _sound_encrypted = true;
+ }
+ if (reel->main_subtitle() && reel->main_subtitle()->encrypted()) {
+ _text_encrypted = true;
+ }
+ for (auto cc: reel->closed_captions()) {
+ if (cc->encrypted()) {
+ _text_encrypted = true;
+ }
+ }
+ }
+
_kdm_valid = true;
LOG_GENERAL_NC("Check that everything encrypted has a key");
diff --git a/src/lib/dcp_examiner.h b/src/lib/dcp_examiner.h
index 0a6045ed0..6bc9793aa 100644
--- a/src/lib/dcp_examiner.h
+++ b/src/lib/dcp_examiner.h
@@ -76,8 +76,16 @@ public:
return _name;
}
- bool encrypted() const {
- return _encrypted;
+ bool picture_encrypted() const {
+ return _picture_encrypted;
+ }
+
+ bool sound_encrypted() const {
+ return _sound_encrypted;
+ }
+
+ bool text_encrypted() const {
+ return _text_encrypted;
}
bool needs_assets() const {
@@ -216,7 +224,9 @@ private:
std::vector<DCPTextTrack> _dcp_subtitle_tracks;
/** the DCPTextTracks for each of our closed captions */
std::vector<DCPTextTrack> _dcp_caption_tracks;
- bool _encrypted = false;
+ bool _picture_encrypted = false;
+ bool _sound_encrypted = false;
+ bool _text_encrypted = false;
bool _needs_assets = false;
bool _kdm_valid = false;
boost::optional<dcp::Standard> _standard;
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 8dc55c590..b8f983add 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -168,7 +168,9 @@ Film::Film(optional<boost::filesystem::path> dir)
, _dcp_content_type(DCPContentType::from_isdcf_name("FTR"))
, _container(Ratio::from_id("185"))
, _resolution(Resolution::TWO_K)
- , _encrypted(false)
+ , _encrypt_picture(false)
+ , _encrypt_sound(false)
+ , _encrypt_text(false)
, _context_id(dcp::make_uuid())
, _video_frame_rate(24)
, _audio_channels(6)
@@ -418,7 +420,11 @@ Film::metadata(bool with_content_paths) const
cxml::add_text_child(root, "Interop", _interop ? "1" : "0");
cxml::add_text_child(root, "VideoEncoding", video_encoding_to_string(_video_encoding));
cxml::add_text_child(root, "LimitToSMPTEBv20", _limit_to_smpte_bv20 ? "1" : "0");
- cxml::add_text_child(root, "Encrypted", _encrypted ? "1" : "0");
+ /* We don't need this any more, but writing it makes the metadata backwards compatible */
+ cxml::add_text_child(root, "Encrypted", encrypted() ? "1" : "0");
+ cxml::add_text_child(root, "EncryptPicture", _encrypt_picture ? "1" : "0");
+ cxml::add_text_child(root, "EncryptSound", _encrypt_sound ? "1" : "0");
+ cxml::add_text_child(root, "EncryptText", _encrypt_text ? "1" : "0");
cxml::add_text_child(root, "Key", _key.hex());
cxml::add_text_child(root, "ContextID", _context_id);
if (_audio_processor) {
@@ -593,7 +599,10 @@ Film::read_metadata(optional<boost::filesystem::path> path)
_video_bit_rate[VideoEncoding::MPEG2] = f.optional_number_child<int64_t>("MPEG2VideoBitRate").get_value_or(Config::instance()->default_video_bit_rate(VideoEncoding::MPEG2));
_video_frame_rate = f.number_child<int>("VideoFrameRate");
_audio_frame_rate = f.optional_number_child<int>("AudioFrameRate").get_value_or(48000);
- _encrypted = f.bool_child("Encrypted");
+ auto encrypted = f.optional_bool_child("Encrypted").get_value_or(false);
+ _encrypt_picture = f.optional_bool_child("EncryptPicture").get_value_or(encrypted);
+ _encrypt_sound = f.optional_bool_child("EncryptSound").get_value_or(encrypted);
+ _encrypt_text = f.optional_bool_child("EncryptText").get_value_or(encrypted);
_audio_channels = f.number_child<int>("AudioChannels");
/* We used to allow odd numbers (and zero) channels, but it's just not worth
the pain.
@@ -1423,12 +1432,29 @@ Film::cpls() const
}
void
-Film::set_encrypted(bool e)
+Film::set_encrypt_picture(bool e)
{
- FilmChangeSignaller ch(this, FilmProperty::ENCRYPTED);
- _encrypted = e;
+ FilmChangeSignaller ch(this, FilmProperty::ENCRYPT_PICTURE);
+ _encrypt_picture = e;
}
+
+void
+Film::set_encrypt_sound(bool e)
+{
+ FilmChangeSignaller ch(this, FilmProperty::ENCRYPT_SOUND);
+ _encrypt_sound = e;
+}
+
+
+void
+Film::set_encrypt_text(bool e)
+{
+ FilmChangeSignaller ch(this, FilmProperty::ENCRYPT_TEXT);
+ _encrypt_text = e;
+}
+
+
ContentList
Film::content() const
{
@@ -1859,7 +1885,7 @@ Film::active_area() const
dcp::DecryptedKDM
Film::make_kdm(boost::filesystem::path cpl_file, dcp::LocalTime from, dcp::LocalTime until) const
{
- if (!_encrypted) {
+ if (!encrypted()) {
throw runtime_error(_("Cannot make a KDM as this project is not encrypted."));
}
@@ -2102,7 +2128,9 @@ Film::use_template(optional<string> name)
_video_bit_rate[encoding] = _template_film->_video_bit_rate[encoding];
}
_video_frame_rate = _template_film->_video_frame_rate;
- _encrypted = _template_film->_encrypted;
+ _encrypt_picture = _template_film->_encrypt_picture;
+ _encrypt_sound = _template_film->_encrypt_sound;
+ _encrypt_text = _template_film->_encrypt_text;
_audio_channels = _template_film->_audio_channels;
_sequence = _template_film->_sequence;
_three_d = _template_film->_three_d;
diff --git a/src/lib/film.h b/src/lib/film.h
index 6d04da9bf..adaff3c52 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -231,7 +231,19 @@ public:
}
bool encrypted() const {
- return _encrypted;
+ return _encrypt_picture || _encrypt_sound || _encrypt_text;
+ }
+
+ bool encrypt_picture() const {
+ return _encrypt_picture;
+ }
+
+ bool encrypt_sound() const {
+ return _encrypt_sound;
+ }
+
+ bool encrypt_text() const {
+ return _encrypt_text;
}
dcp::Key key() const {
@@ -392,7 +404,9 @@ public:
void set_dcp_content_type(DCPContentType const *);
void set_container(Ratio c, bool user_explicit = true);
void set_resolution(Resolution, bool user_explicit = true);
- void set_encrypted(bool);
+ void set_encrypt_picture(bool);
+ void set_encrypt_sound(bool);
+ void set_encrypt_text(bool);
void set_video_bit_rate(VideoEncoding encoding, int64_t);
void set_video_frame_rate(int rate, bool user_explicit = false);
void set_audio_channels(int);
@@ -512,7 +526,12 @@ private:
Ratio _container;
/** DCP resolution (2K or 4K) */
Resolution _resolution;
- bool _encrypted;
+ /** Encrypt picture assets */
+ bool _encrypt_picture;
+ /** Encrypt sound assets */
+ bool _encrypt_sound;
+ /** Encrypt text (subtitle/closed-caption) assets */
+ bool _encrypt_text;
dcp::Key _key;
/** context ID used when encrypting picture assets; we keep it so that we can
* re-start picture MXF encodes.
diff --git a/src/lib/film_property.h b/src/lib/film_property.h
index ebda0e807..5c38ae986 100644
--- a/src/lib/film_property.h
+++ b/src/lib/film_property.h
@@ -38,7 +38,9 @@ enum class FilmProperty {
DCP_CONTENT_TYPE,
CONTAINER,
RESOLUTION,
- ENCRYPTED,
+ ENCRYPT_PICTURE,
+ ENCRYPT_SOUND,
+ ENCRYPT_TEXT,
VIDEO_BIT_RATE,
VIDEO_FRAME_RATE,
AUDIO_FRAME_RATE,
diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc
index 01a798676..1d5a16075 100644
--- a/src/lib/reel_writer.cc
+++ b/src/lib/reel_writer.cc
@@ -146,7 +146,7 @@ ReelWriter::ReelWriter(
asset->set_size(film()->frame_size());
asset->set_metadata(mxf_metadata());
- if (film()->encrypted()) {
+ if (film()->encrypt_picture()) {
asset->set_key(film()->key());
asset->set_context_id(film()->context_id());
}
@@ -218,7 +218,7 @@ ReelWriter::ReelWriter(
_sound_asset->set_metadata(mxf_metadata());
- if (film()->encrypted()) {
+ if (film()->encrypt_sound()) {
_sound_asset->set_key(film()->key());
}
@@ -310,7 +310,7 @@ ReelWriter::write(shared_ptr<const dcp::AtmosFrame> atmos, AtmosMetadata metadat
{
if (!_atmos_asset) {
_atmos_asset = metadata.create(dcp::Fraction(film()->video_frame_rate(), 1));
- if (film()->encrypted()) {
+ if (film()->encrypt_sound()) {
_atmos_asset->set_key(film()->key());
}
_atmos_asset_writer = _atmos_asset->start_write(
@@ -820,7 +820,7 @@ ReelWriter::empty_text_asset(TextType type, optional<DCPTextTrack> track, bool w
s->set_reel_number(_reel_index + 1);
s->set_time_code_rate(film()->video_frame_rate());
s->set_start_time(dcp::Time());
- if (film()->encrypted()) {
+ if (film()->encrypt_text()) {
s->set_key(film()->key());
}
asset = s;
diff --git a/src/lib/writer.cc b/src/lib/writer.cc
index 7121c594b..41e831e94 100644
--- a/src/lib/writer.cc
+++ b/src/lib/writer.cc
@@ -723,7 +723,7 @@ Writer::finish()
bool
Writer::can_fake_write(Frame frame) const
{
- if (film()->encrypted()) {
+ if (film()->encrypt_picture()) {
/* We need to re-write the frame because the asset ID is embedded in the HMAC... I think... */
return false;
}
diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc
index 755ecfbaa..96b4b3cb5 100644
--- a/src/wx/dcp_panel.cc
+++ b/src/wx/dcp_panel.cc
@@ -27,6 +27,7 @@
#include "dcpomatic_button.h"
#include "dcpomatic_choice.h"
#include "dcpomatic_spin_ctrl.h"
+#include "encryption_settings_dialog.h"
#include "film_viewer.h"
#include "focus_manager.h"
#include "interop_metadata_dialog.h"
@@ -103,6 +104,7 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
_dcp_content_type = new Choice(_panel);
_encrypted = new CheckBox(_panel, _("Encrypted"));
+ _encryption_settings = new Button(_panel, _("Encryption settings..."));
wxClientDC dc(_panel);
auto size = dc.GetTextExtent(char_to_wx("GGGGGGGG..."));
@@ -126,6 +128,7 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
_copy_isdcf_name_button->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::copy_isdcf_name_button_clicked, this));
_dcp_content_type->Bind(wxEVT_CHOICE, boost::bind(&DCPPanel::dcp_content_type_changed, this));
_encrypted->bind(&DCPPanel::encrypted_toggled, this);
+ _encryption_settings->bind(&DCPPanel::encryption_settings_clicked, this);
_standard->Bind(wxEVT_CHOICE, boost::bind(&DCPPanel::standard_changed, this));
_markers->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::markers_clicked, this));
_metadata->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::metadata_clicked, this));
@@ -242,7 +245,8 @@ DCPPanel::add_to_grid()
_grid->Add(_dcp_content_type, wxGBPosition(r, 1));
++r;
- _grid->Add(_encrypted, wxGBPosition(r, 0), wxGBSpan(1, 2));
+ _grid->Add(_encrypted, wxGBPosition(r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+ _grid->Add(_encryption_settings, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++r;
add_label_to_sizer(_grid, _standard_label, true, wxGBPosition(r, 0));
@@ -287,7 +291,23 @@ DCPPanel::encrypted_toggled()
return;
}
- _film->set_encrypted(_encrypted->GetValue());
+ auto new_value = !_film->encrypted();
+
+ _film->set_encrypt_picture(new_value);
+ _film->set_encrypt_sound(new_value);
+ _film->set_encrypt_text(new_value);
+}
+
+
+void
+DCPPanel::encryption_settings_clicked()
+{
+ EncryptionSettingsDialog dialog(_panel, _film);
+ dialog.ShowModal();
+
+ _film->set_encrypt_picture(dialog.picture());
+ _film->set_encrypt_sound(dialog.sound());
+ _film->set_encrypt_text(dialog.text());
}
@@ -394,8 +414,11 @@ DCPPanel::film_changed(FilmProperty p)
setup_dcp_name();
break;
}
- case FilmProperty::ENCRYPTED:
+ case FilmProperty::ENCRYPT_PICTURE:
+ case FilmProperty::ENCRYPT_SOUND:
+ case FilmProperty::ENCRYPT_TEXT:
checked_set(_encrypted, _film->encrypted());
+ setup_sensitivity();
break;
case FilmProperty::RESOLUTION:
checked_set(_resolution, _film->resolution() == Resolution::TWO_K ? 0 : 1);
@@ -624,7 +647,9 @@ DCPPanel::set_film(shared_ptr<Film> film)
film_changed(FilmProperty::DCP_CONTENT_TYPE);
film_changed(FilmProperty::CONTAINER);
film_changed(FilmProperty::RESOLUTION);
- film_changed(FilmProperty::ENCRYPTED);
+ film_changed(FilmProperty::ENCRYPT_PICTURE);
+ film_changed(FilmProperty::ENCRYPT_SOUND);
+ film_changed(FilmProperty::ENCRYPT_TEXT);
film_changed(FilmProperty::VIDEO_BIT_RATE);
film_changed(FilmProperty::VIDEO_FRAME_RATE);
film_changed(FilmProperty::AUDIO_CHANNELS);
@@ -664,6 +689,7 @@ DCPPanel::setup_sensitivity()
_audio_language->Enable (_enable_audio_language->GetValue());
_edit_audio_language->Enable (_enable_audio_language->GetValue());
_encrypted->Enable (_generally_sensitive);
+ _encryption_settings->Enable (_generally_sensitive && _encrypted->GetValue());
_markers->Enable (_generally_sensitive && _film && !_film->interop());
_metadata->Enable (_generally_sensitive);
_reels->Enable (_generally_sensitive && _film);
diff --git a/src/wx/dcp_panel.h b/src/wx/dcp_panel.h
index 2196e8927..29ad83eaf 100644
--- a/src/wx/dcp_panel.h
+++ b/src/wx/dcp_panel.h
@@ -81,6 +81,7 @@ private:
void three_d_changed();
void standard_changed();
void encrypted_toggled();
+ void encryption_settings_clicked();
void audio_processor_changed();
void show_audio_clicked();
void markers_clicked();
@@ -153,8 +154,9 @@ private:
wxStaticText* _standard_label;
Choice* _standard;
CheckBox* _encrypted;
- wxButton* _markers;
- wxButton* _metadata;
+ Button* _encryption_settings;
+ Button* _markers;
+ Button* _metadata;
Button* _reels;
wxSizer* _audio_panel_sizer;
diff --git a/src/wx/encryption_settings_dialog.cc b/src/wx/encryption_settings_dialog.cc
new file mode 100644
index 000000000..2fdb689c9
--- /dev/null
+++ b/src/wx/encryption_settings_dialog.cc
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2025 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 "check_box.h"
+#include "encryption_settings_dialog.h"
+#include "lib/film.h"
+
+
+using std::shared_ptr;
+
+
+EncryptionSettingsDialog::EncryptionSettingsDialog(wxWindow* parent, shared_ptr<const Film> film)
+ : TableDialog(parent, _("Encryption settings"), 1, 0, true)
+{
+ _picture = add(new CheckBox(this, _("Encrypt picture")));
+ _sound = add(new CheckBox(this, _("Encrypt sound")));
+ _text = add(new CheckBox(this, _("Encrypt text")));
+
+ layout();
+
+ _picture->set(film->encrypt_picture());
+ _sound->set(film->encrypt_sound());
+ _text->set(film->encrypt_text());
+}
+
+
+bool
+EncryptionSettingsDialog::picture() const
+{
+ return _picture->get();
+}
+
+
+bool
+EncryptionSettingsDialog::sound() const
+{
+ return _sound->get();
+}
+
+
+bool
+EncryptionSettingsDialog::text() const
+{
+ return _text->get();
+}
+
diff --git a/src/wx/encryption_settings_dialog.h b/src/wx/encryption_settings_dialog.h
new file mode 100644
index 000000000..4ea3151c9
--- /dev/null
+++ b/src/wx/encryption_settings_dialog.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (C) 2025 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 "table_dialog.h"
+
+
+class CheckBox;
+class Film;
+
+
+class EncryptionSettingsDialog : public TableDialog
+{
+public:
+ EncryptionSettingsDialog(wxWindow* parent, std::shared_ptr<const Film> film);
+
+ bool picture() const;
+ bool sound() const;
+ bool text() const;
+
+private:
+ CheckBox* _picture;
+ CheckBox* _sound;
+ CheckBox* _text;
+};
+
diff --git a/src/wx/wscript b/src/wx/wscript
index ace747514..d3f3db88a 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -80,6 +80,7 @@ sources = """
drive_wipe_warning_dialog.cc
email_dialog.cc
email_preferences_page.cc
+ encryption_settings_dialog.cc
export_subtitles_dialog.cc
export_video_file_dialog.cc
extra_kdm_email_dialog.cc
diff --git a/test/atmos_test.cc b/test/atmos_test.cc
index 33a427321..776cc9310 100644
--- a/test/atmos_test.cc
+++ b/test/atmos_test.cc
@@ -68,7 +68,9 @@ BOOST_AUTO_TEST_CASE(atmos_encrypted_passthrough_test)
auto content = content_factory(TestPaths::private_data() / "atmos_asset.mxf");
auto film = new_test_film("atmos_encrypted_passthrough_test", content, &cl);
- film->set_encrypted(true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(true);
film->_key = dcp::Key("4fac12927eb122af1c2781aa91f3a4cc");
make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
diff --git a/test/data b/test/data
-Subproject 024cb24f49525e0cc172d4e91d75e0c4d81ef6e
+Subproject df51596458d402f40a5afaf943225ce7e95cdf6
diff --git a/test/dcp_decoder_test.cc b/test/dcp_decoder_test.cc
index cf9950aae..87cea6691 100644
--- a/test/dcp_decoder_test.cc
+++ b/test/dcp_decoder_test.cc
@@ -64,7 +64,9 @@ BOOST_AUTO_TEST_CASE (check_reuse_old_data_test)
auto encrypted = new_test_film("check_reuse_old_data_decrypted");
encrypted->examine_and_add_content(content_factory("test/data/flat_red.png"));
BOOST_REQUIRE (!wait_for_jobs());
- encrypted->set_encrypted (true);
+ encrypted->set_encrypt_picture(true);
+ encrypted->set_encrypt_sound(true);
+ encrypted->set_encrypt_text(true);
make_and_verify_dcp (encrypted);
dcp::DCP encrypted_dcp (encrypted->dir(encrypted->dcp_name()));
diff --git a/test/dcp_digest_file_test.cc b/test/dcp_digest_file_test.cc
index 5c12fbb33..2e717896f 100644
--- a/test/dcp_digest_file_test.cc
+++ b/test/dcp_digest_file_test.cc
@@ -67,7 +67,8 @@ BOOST_AUTO_TEST_CASE (dcp_digest_file_test2)
auto red = content_factory("test/data/flat_red.png");
auto ov = new_test_film("dcp_digest_file_test2_ov", red);
- ov->set_encrypted (true);
+ ov->set_encrypt_picture(true);
+ ov->set_encrypt_sound(true);
make_and_verify_dcp (ov);
auto ov_key_check = get_key_from_digest ("build/test/dcp_digest_file_test2_ov/" + ov->dcp_name() + ".dcpdig");
@@ -91,7 +92,8 @@ BOOST_AUTO_TEST_CASE (dcp_digest_file_test2)
ov_dcp->set_reference_video (true);
ov_dcp->set_reference_audio (true);
auto vf = new_test_film("dcp_digest_file_test2_vf", { ov_dcp });
- vf->set_encrypted (true);
+ vf->set_encrypt_picture(true);
+ vf->set_encrypt_sound(true);
make_and_verify_dcp(vf, {dcp::VerificationNote::Code::EXTERNAL_ASSET}, false);
auto vf_key_check = get_key_from_digest ("build/test/dcp_digest_file_test2_vf/" + vf->dcp_name() + ".dcpdig");
diff --git a/test/encryption_test.cc b/test/encryption_test.cc
index 97359cd44..31e31d53c 100644
--- a/test/encryption_test.cc
+++ b/test/encryption_test.cc
@@ -27,9 +27,16 @@
#include "test.h"
#include <dcp/cpl.h>
#include <dcp/dcp.h>
+#include <dcp/j2k_transcode.h>
+#include <dcp/mono_j2k_picture_asset.h>
+#include <dcp/reel.h>
+#include <dcp/reel_picture_asset.h>
+#include <dcp/reel_sound_asset.h>
+#include <dcp/reel_text_asset.h>
#include <boost/test/unit_test.hpp>
+using std::dynamic_pointer_cast;
using std::make_shared;
@@ -38,7 +45,9 @@ BOOST_AUTO_TEST_CASE (smpte_dcp_with_subtitles_can_be_decrypted)
auto content = content_factory("test/data/15s.srt");
auto film = new_test_film("smpte_dcp_with_subtitles_can_be_decrypted", content);
film->set_interop (false);
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(true);
make_and_verify_dcp (
film,
{
@@ -67,3 +76,98 @@ BOOST_AUTO_TEST_CASE (smpte_dcp_with_subtitles_can_be_decrypted)
BOOST_CHECK (examiner.kdm_valid());
}
+
+BOOST_AUTO_TEST_CASE(encrypt_only_picture)
+{
+ auto picture = content_factory("test/data/flat_red.png")[0];
+ auto text = content_factory("test/data/15s.srt")[0];
+ auto film = new_test_film("encrypt_only_picture", { picture, text });
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(false);
+ film->set_encrypt_text(false);
+ /* clairmeta says "Encrypted is not coherent for all reels" */
+ make_and_verify_dcp(
+ film,
+ {
+ dcp::VerificationNote::Code::MISSING_CPL_METADATA,
+ dcp::VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED,
+ dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED,
+ dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
+ dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME,
+ }, true, false);
+
+ dcp::DCP dcp(film->dir(film->dcp_name()));
+ dcp.read();
+ BOOST_REQUIRE_EQUAL(dcp.cpls().size(), 1U);
+ auto cpl = dcp.cpls()[0];
+ BOOST_REQUIRE(cpl->file());
+
+ auto dcp_picture = dynamic_pointer_cast<dcp::MonoJ2KPictureAsset>(cpl->reels()[0]->main_picture()->asset());
+ auto reader = dcp_picture->start_read();
+ auto frame = reader->get_frame(0);
+ BOOST_CHECK_THROW(dcp::decompress_j2k(frame->data(), frame->size(), 0), dcp::J2KDecompressionError);
+}
+
+
+BOOST_AUTO_TEST_CASE(encrypt_only_sound)
+{
+ auto picture = content_factory("test/data/flat_red.png")[0];
+ auto text = content_factory("test/data/15s.srt")[0];
+ auto film = new_test_film("encrypt_only_picture", { picture, text });
+ film->set_encrypt_picture(false);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(false);
+ /* clairmeta says "Encrypted is not coherent for all reels" */
+ make_and_verify_dcp(
+ film,
+ {
+ dcp::VerificationNote::Code::MISSING_CPL_METADATA,
+ dcp::VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED,
+ dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED,
+ dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
+ dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME,
+ }, true, false);
+
+ dcp::DCP dcp(film->dir(film->dcp_name()));
+ dcp.read();
+ BOOST_REQUIRE_EQUAL(dcp.cpls().size(), 1U);
+ auto cpl = dcp.cpls()[0];
+ BOOST_REQUIRE(cpl->file());
+
+ auto dcp_sound = dynamic_pointer_cast<dcp::SoundAsset>(cpl->reels()[0]->main_sound()->asset());
+ auto reader = dcp_sound->start_read();
+ auto frame = reader->get_frame(0);
+ int zeros = 0;
+ for (int i = 0; i < 1024; ++i) {
+ zeros += frame->data()[i] == 0;
+ }
+ BOOST_CHECK(zeros != 1024);
+}
+
+
+BOOST_AUTO_TEST_CASE(encrypt_only_text)
+{
+ auto picture = content_factory("test/data/flat_red.png")[0];
+ auto text = content_factory("test/data/15s.srt")[0];
+ auto film = new_test_film("encrypt_only_picture", { picture, text });
+ film->set_encrypt_picture(false);
+ film->set_encrypt_sound(false);
+ film->set_encrypt_text(true);
+ make_and_verify_dcp(
+ film,
+ {
+ dcp::VerificationNote::Code::MISSING_CPL_METADATA,
+ dcp::VerificationNote::Code::MISSED_CHECK_OF_ENCRYPTED,
+ dcp::VerificationNote::Code::PARTIALLY_ENCRYPTED,
+ });
+
+ dcp::DCP dcp(film->dir(film->dcp_name()));
+ dcp.read();
+ BOOST_REQUIRE_EQUAL(dcp.cpls().size(), 1U);
+ auto cpl = dcp.cpls()[0];
+ BOOST_REQUIRE(cpl->file());
+
+ auto dcp_subtitle = dynamic_pointer_cast<dcp::TextAsset>(cpl->reels()[0]->main_subtitle()->asset());
+ BOOST_CHECK_THROW(dcp_subtitle->xml_as_string(), dcp::ProgrammingError);
+}
+
diff --git a/test/import_dcp_test.cc b/test/import_dcp_test.cc
index 8782e7f73..0201594c7 100644
--- a/test/import_dcp_test.cc
+++ b/test/import_dcp_test.cc
@@ -58,7 +58,8 @@ BOOST_AUTO_TEST_CASE (import_dcp_test)
auto c = make_shared<FFmpegContent>("test/data/test.mp4");
auto A = new_test_film("import_dcp_test", { c });
- A->set_encrypted (true);
+ A->set_encrypt_picture(true);
+ A->set_encrypt_sound(true);
A->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
make_and_verify_dcp (A);
diff --git a/test/kdm_cli_test.cc b/test/kdm_cli_test.cc
index 4292da17e..4bc3ddf46 100644
--- a/test/kdm_cli_test.cc
+++ b/test/kdm_cli_test.cc
@@ -276,7 +276,9 @@ BOOST_AUTO_TEST_CASE(kdm_cli_specify_cert)
boost::filesystem::remove(kdm_filename, ec);
auto film = new_test_film("kdm_cli_specify_cert", content_factory("test/data/flat_red.png"));
- film->set_encrypted(true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(true);
film->set_name("KDMCLI");
film->set_use_isdcf_name(false);
make_and_verify_dcp(film);
diff --git a/test/kdm_naming_test.cc b/test/kdm_naming_test.cc
index 2308e6ac6..efc96b2c2 100644
--- a/test/kdm_naming_test.cc
+++ b/test/kdm_naming_test.cc
@@ -89,7 +89,8 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
film->set_name ("my_great_film");
film->examine_and_add_content(content_factory("test/data/flat_black.png"));
BOOST_REQUIRE (!wait_for_jobs());
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
make_and_verify_dcp (film);
auto cpls = film->cpls ();
BOOST_REQUIRE(cpls.size() == 1);
@@ -156,7 +157,8 @@ BOOST_AUTO_TEST_CASE(directory_kdm_naming_test)
);
film->set_name ("my_great_film");
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
make_and_verify_dcp (film);
auto cpls = film->cpls ();
BOOST_REQUIRE(cpls.size() == 1);
diff --git a/test/player_test.cc b/test/player_test.cc
index ab4560ffa..684cede04 100644
--- a/test/player_test.cc
+++ b/test/player_test.cc
@@ -463,7 +463,9 @@ BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
auto film = new_test_film("encrypted_dcp_with_no_kdm_gives_no_butler_error", { content });
int constexpr length = 24 * 25;
content->video->set_length(length);
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
+ film->set_encrypt_text(true);
make_and_verify_dcp (
film,
{
diff --git a/test/recover_test.cc b/test/recover_test.cc
index ba06fcde9..73d54b017 100644
--- a/test/recover_test.cc
+++ b/test/recover_test.cc
@@ -142,7 +142,8 @@ BOOST_AUTO_TEST_CASE (recover_test_2d_encrypted, * boost::unit_test::depends_on(
{
auto content = make_shared<FFmpegContent>("test/data/count300bd24.m2ts");
auto film = new_test_film("recover_test_2d_encrypted", { content });
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
film->_key = dcp::Key("eafcb91c9f5472edf01f3a2404c57258");
film->set_video_bit_rate(VideoEncoding::JPEG2000, 100000000);
diff --git a/test/remake_id_test.cc b/test/remake_id_test.cc
index 0673a4283..579a711a8 100644
--- a/test/remake_id_test.cc
+++ b/test/remake_id_test.cc
@@ -69,7 +69,8 @@ BOOST_AUTO_TEST_CASE (remake_id_test2)
/* Make a DCP */
auto content = content_factory("test/data/flat_red.png");
auto film = new_test_film("remake_id_test2_1", content);
- film->set_encrypted (true);
+ film->set_encrypt_picture(true);
+ film->set_encrypt_sound(true);
make_and_verify_dcp (film);
/* Remove and remake it */
diff --git a/test/vf_kdm_test.cc b/test/vf_kdm_test.cc
index 3fd8bc327..a5989088b 100644
--- a/test/vf_kdm_test.cc
+++ b/test/vf_kdm_test.cc
@@ -55,7 +55,8 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
auto A = new_test_film("vf_kdm_test_ov", { c });
A->set_interop (true);
A->set_dcp_content_type(DCPContentType::from_isdcf_name("TLR"));
- A->set_encrypted (true);
+ A->set_encrypt_picture(true);
+ A->set_encrypt_sound(true);
make_and_verify_dcp (A, {dcp::VerificationNote::Code::INVALID_STANDARD});
dcp::DCP A_dcp ("build/test/vf_kdm_test_ov/" + A->dcp_name());
@@ -79,7 +80,8 @@ BOOST_AUTO_TEST_CASE (vf_kdm_test)
B->set_interop(true);
d->set_reference_video (true);
- B->set_encrypted (true);
+ B->set_encrypt_picture(true);
+ B->set_encrypt_sound(true);
make_and_verify_dcp (B, {dcp::VerificationNote::Code::INVALID_STANDARD, dcp::VerificationNote::Code::EXTERNAL_ASSET});
dcp::DCP B_dcp ("build/test/vf_kdm_test_vf/" + B->dcp_name());