diff options
| author | Carl Hetherington <cth@carlh.net> | 2024-08-02 18:32:50 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2024-08-02 18:32:50 +0200 |
| commit | 30253cbddbe1167d1b36d4f4185681ceeb6e26ba (patch) | |
| tree | 86ffb12b9e48d9d74512aa2b2c3952e25cdf9bad | |
| parent | 95abcc7d18997c1391423d15506bf03ab20997e6 (diff) | |
WIP: hacks.shared-ptr
| -rw-r--r-- | src/lib/j2k_image_proxy.cc | 246 | ||||
| -rw-r--r-- | src/lib/j2k_image_proxy.h | 183 | ||||
| -rw-r--r-- | src/lib/map_cli.cc | 21 | ||||
| -rw-r--r-- | src/lib/wscript | 1 |
4 files changed, 187 insertions, 264 deletions
diff --git a/src/lib/j2k_image_proxy.cc b/src/lib/j2k_image_proxy.cc deleted file mode 100644 index 023723c8b..000000000 --- a/src/lib/j2k_image_proxy.cc +++ /dev/null @@ -1,246 +0,0 @@ -/* - Copyright (C) 2014-2021 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 "dcpomatic_assert.h" -#include "dcpomatic_socket.h" -#include "image.h" -#include "j2k_image_proxy.h" -#include <dcp/colour_conversion.h> -#include <dcp/j2k_transcode.h> -#include <dcp/mono_j2k_picture_frame.h> -#include <dcp/openjpeg_image.h> -#include <dcp/raw_convert.h> -#include <dcp/rgb_xyz.h> -#include <dcp/stereo_j2k_picture_frame.h> -#include <dcp/warnings.h> -#include <libcxml/cxml.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS -#include <iostream> - -#include "i18n.h" - - -using std::cout; -using std::dynamic_pointer_cast; -using std::make_shared; -using std::max; -using std::shared_ptr; -using std::string; -using boost::optional; -using dcp::ArrayData; -using dcp::raw_convert; - - -/** Construct a J2KImageProxy from a JPEG2000 file */ -J2KImageProxy::J2KImageProxy (boost::filesystem::path path, dcp::Size size, AVPixelFormat pixel_format) - : _data (new dcp::ArrayData(path)) - , _size (size) - , _pixel_format (pixel_format) - , _error (false) -{ - /* ::image assumes 16bpp */ - DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); -} - - -J2KImageProxy::J2KImageProxy ( - shared_ptr<const dcp::MonoJ2KPictureFrame> frame, - dcp::Size size, - AVPixelFormat pixel_format, - optional<int> forced_reduction - ) - : _data (frame) - , _size (size) - , _pixel_format (pixel_format) - , _forced_reduction (forced_reduction) - , _error (false) -{ - /* ::image assumes 16bpp */ - DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); -} - - -J2KImageProxy::J2KImageProxy ( - shared_ptr<const dcp::StereoJ2KPictureFrame> frame, - dcp::Size size, - dcp::Eye eye, - AVPixelFormat pixel_format, - optional<int> forced_reduction - ) - : _data (eye == dcp::Eye::LEFT ? frame->left() : frame->right()) - , _size (size) - , _eye (eye) - , _pixel_format (pixel_format) - , _forced_reduction (forced_reduction) - , _error (false) -{ - /* ::image assumes 16bpp */ - DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); -} - - -J2KImageProxy::J2KImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket) - : _error (false) -{ - _size = dcp::Size (xml->number_child<int>("Width"), xml->number_child<int>("Height")); - if (xml->optional_number_child<int>("Eye")) { - _eye = static_cast<dcp::Eye>(xml->number_child<int>("Eye")); - } - auto data = make_shared<ArrayData>(xml->number_child<int>("Size")); - /* This only matters when we are using J2KImageProxy for the preview, which - will never use this constructor (which is only used for passing data to - encode servers). So we can put anything in here. It's a bit of a hack. - */ - _pixel_format = AV_PIX_FMT_XYZ12LE; - socket->read (data->data(), data->size()); - _data = data; -} - - -int -J2KImageProxy::prepare (Image::Alignment alignment, optional<dcp::Size> target_size) const -{ - boost::mutex::scoped_lock lm (_mutex); - - if (_image && target_size == _target_size) { - DCPOMATIC_ASSERT (_reduce); - return *_reduce; - } - - int reduce = 0; - - if (_forced_reduction) { - reduce = *_forced_reduction; - } else { - while (target_size && (_size.width / pow(2, reduce)) > target_size->width && (_size.height / pow(2, reduce)) > target_size->height) { - ++reduce; - } - - --reduce; - reduce = max (0, reduce); - } - - try { - /* XXX: should check that potentially trashing _data here doesn't matter */ - auto decompressed = dcp::decompress_j2k (const_cast<uint8_t*>(_data->data()), _data->size(), reduce); - _image = make_shared<Image>(_pixel_format, decompressed->size(), alignment); - - int const shift = 16 - decompressed->precision (0); - - /* Copy data in whatever format (sRGB or XYZ) into our Image; I'm assuming - the data is 12-bit either way. - */ - - int const width = decompressed->size().width; - - int p = 0; - int* decomp_0 = decompressed->data (0); - int* decomp_1 = decompressed->data (1); - int* decomp_2 = decompressed->data (2); - for (int y = 0; y < decompressed->size().height; ++y) { - auto q = reinterpret_cast<uint16_t *>(_image->data()[0] + y * _image->stride()[0]); - for (int x = 0; x < width; ++x) { - *q++ = decomp_0[p] << shift; - *q++ = decomp_1[p] << shift; - *q++ = decomp_2[p] << shift; - ++p; - } - } - } catch (dcp::J2KDecompressionError& e) { - _image = make_shared<Image>(_pixel_format, _size, alignment); - _image->make_black (); - _error = true; - } - - _target_size = target_size; - _reduce = reduce; - - return reduce; -} - - -ImageProxy::Result -J2KImageProxy::image (Image::Alignment alignment, optional<dcp::Size> target_size) const -{ - int const r = prepare (alignment, target_size); - - /* I think this is safe without a lock on mutex. _image is guaranteed to be - set up when prepare() has happened. - */ - return Result (_image, r, _error); -} - - -void -J2KImageProxy::add_metadata(xmlpp::Element* element) const -{ - cxml::add_text_child(element, "Type", N_("J2K")); - cxml::add_text_child(element, "Width", raw_convert<string>(_size.width)); - cxml::add_text_child(element, "Height", raw_convert<string>(_size.height)); - if (_eye) { - cxml::add_text_child(element, "Eye", raw_convert<string>(static_cast<int>(_eye.get()))); - } - cxml::add_text_child(element, "Size", raw_convert<string>(_data->size())); -} - - -void -J2KImageProxy::write_to_socket (shared_ptr<Socket> socket) const -{ - socket->write (_data->data(), _data->size()); -} - - -bool -J2KImageProxy::same (shared_ptr<const ImageProxy> other) const -{ - auto jp = dynamic_pointer_cast<const J2KImageProxy>(other); - if (!jp) { - return false; - } - - return *_data == *jp->_data; -} - - -J2KImageProxy::J2KImageProxy (ArrayData data, dcp::Size size, AVPixelFormat pixel_format) - : _data (new ArrayData(data)) - , _size (size) - , _pixel_format (pixel_format) - , _error (false) -{ - /* ::image assumes 16bpp */ - DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); -} - - -size_t -J2KImageProxy::memory_used () const -{ - size_t m = _data->size(); - if (_image) { - /* 3 components, 16-bits per pixel */ - m += 3 * 2 * _image->size().width * _image->size().height; - } - return m; -} diff --git a/src/lib/j2k_image_proxy.h b/src/lib/j2k_image_proxy.h index 1d2d5cc21..0343f929c 100644 --- a/src/lib/j2k_image_proxy.h +++ b/src/lib/j2k_image_proxy.h @@ -19,8 +19,11 @@ */ +#include "dcpomatic_assert.h" #include "image_proxy.h" #include <dcp/array_data.h> +#include <dcp/mono_j2k_picture_frame.h> +#include <dcp/types.h> #include <dcp/util.h> #include <boost/thread/mutex.hpp> @@ -31,41 +34,181 @@ namespace dcp { } +template <class Frame> class J2KImageProxy : public ImageProxy { public: - J2KImageProxy (boost::filesystem::path path, dcp::Size, AVPixelFormat pixel_format); + J2KImageProxy(boost::filesystem::path path, dcp::Size, AVPixelFormat pixel_format) + : _data(path) + , _size (size) + , _pixel_format (pixel_format) + , _error (false) + { + /* ::image assumes 16bpp */ + DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); + } J2KImageProxy ( - std::shared_ptr<const dcp::MonoJ2KPictureFrame> frame, + dcp::MonoJ2KPictureFrame frame, dcp::Size, AVPixelFormat pixel_format, boost::optional<int> forced_reduction - ); + ) + : _data (frame) + , _size (size) + , _pixel_format (pixel_format) + , _forced_reduction (forced_reduction) + , _error (false) + { + /* ::image assumes 16bpp */ + DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); + } J2KImageProxy ( - std::shared_ptr<const dcp::StereoJ2KPictureFrame> frame, - dcp::Size, - dcp::Eye, + dcp::StereoJ2KPictureFrame frame, + dcp::Size size, + dcp::Eye eye, AVPixelFormat pixel_format, boost::optional<int> forced_reduction - ); + ) + : _data (eye == dcp::Eye::LEFT ? frame->left() : frame->right()) + , _size (size) + , _eye (eye) + , _pixel_format (pixel_format) + , _forced_reduction (forced_reduction) + , _error (false) + { + /* ::image assumes 16bpp */ + DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); + } - J2KImageProxy (std::shared_ptr<cxml::Node> xml, std::shared_ptr<Socket> socket); + + J2KImageProxy (std::shared_ptr<cxml::Node> xml, std::shared_ptr<Socket> socket) + { + _size = dcp::Size (xml->number_child<int>("Width"), xml->number_child<int>("Height")); + if (xml->optional_number_child<int>("Eye")) { + _eye = static_cast<dcp::Eye>(xml->number_child<int>("Eye")); + } + auto data = make_shared<ArrayData>(xml->number_child<int>("Size")); + /* This only matters when we are using J2KImageProxy for the preview, which + will never use this constructor (which is only used for passing data to + encode servers). So we can put anything in here. It's a bit of a hack. + */ + _pixel_format = AV_PIX_FMT_XYZ12LE; + socket->read(data->data(), data->size()); + _data = data; + } /* For tests */ - J2KImageProxy (dcp::ArrayData data, dcp::Size size, AVPixelFormat pixel_format); + J2KImageProxy(dcp::ArrayData data, dcp::Size size, AVPixelFormat pixel_format) + : _data(data) + , _size(size) + , _pixel_format(pixel_format) + , _error(false) + { + /* ::image assumes 16bpp */ + DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); + } Result image ( Image::Alignment alignment, boost::optional<dcp::Size> size = boost::optional<dcp::Size> () - ) const override; + ) const override + { + int const r = prepare (alignment, target_size); + + /* I think this is safe without a lock on mutex. _image is guaranteed to be + set up when prepare() has happened. + */ + return Result(_image, r, _error); + } + + void add_metadata(xmlpp::Element*) const override + { + cxml::add_text_child(element, "Type", N_("J2K")); + cxml::add_text_child(element, "Width", raw_convert<string>(_size.width)); + cxml::add_text_child(element, "Height", raw_convert<string>(_size.height)); + if (_eye) { + cxml::add_text_child(element, "Eye", raw_convert<string>(static_cast<int>(_eye.get()))); + } + cxml::add_text_child(element, "Size", raw_convert<string>(_data.size())); + } + + void write_to_socket (std::shared_ptr<Socket> override) const override + { + socket->write(_data.data(), _data.size()); + } - void add_metadata(xmlpp::Element*) const override; - void write_to_socket (std::shared_ptr<Socket> override) const override; /** @return true if our image is definitely the same as another, false if it is probably not */ - bool same (std::shared_ptr<const ImageProxy>) const override; - int prepare (Image::Alignment alignment, boost::optional<dcp::Size> = boost::optional<dcp::Size>()) const override; + bool same(std::shared_ptr<const ImageProxy>) const override + { + auto jp = dynamic_pointer_cast<const J2KImageProxy>(other); + if (!jp) { + return false; + } + + return _data == *jp->_data; + } + + int prepare(Image::Alignment alignment, boost::optional<dcp::Size> = boost::optional<dcp::Size>()) const override + { + boost::mutex::scoped_lock lm (_mutex); + + if (_image && target_size == _target_size) { + DCPOMATIC_ASSERT (_reduce); + return *_reduce; + } + + int reduce = 0; + + if (_forced_reduction) { + reduce = *_forced_reduction; + } else { + while (target_size && (_size.width / pow(2, reduce)) > target_size->width && (_size.height / pow(2, reduce)) > target_size->height) { + ++reduce; + } + + --reduce; + reduce = max (0, reduce); + } + + try { + /* XXX: should check that potentially trashing _data here doesn't matter */ + auto decompressed = dcp::decompress_j2k (const_cast<uint8_t*>(_data->data()), _data->size(), reduce); + _image = make_shared<Image>(_pixel_format, decompressed->size(), alignment); + + int const shift = 16 - decompressed->precision (0); + + /* Copy data in whatever format (sRGB or XYZ) into our Image; I'm assuming + the data is 12-bit either way. + */ + + int const width = decompressed->size().width; + + int p = 0; + int* decomp_0 = decompressed->data (0); + int* decomp_1 = decompressed->data (1); + int* decomp_2 = decompressed->data (2); + for (int y = 0; y < decompressed->size().height; ++y) { + auto q = reinterpret_cast<uint16_t *>(_image->data()[0] + y * _image->stride()[0]); + for (int x = 0; x < width; ++x) { + *q++ = decomp_0[p] << shift; + *q++ = decomp_1[p] << shift; + *q++ = decomp_2[p] << shift; + ++p; + } + } + } catch (dcp::J2KDecompressionError& e) { + _image = make_shared<Image>(_pixel_format, _size, alignment); + _image->make_black (); + _error = true; + } + + _target_size = target_size; + _reduce = reduce; + + return reduce; + } std::shared_ptr<const dcp::Data> j2k () const { return _data; @@ -79,10 +222,18 @@ public: return _eye; } - size_t memory_used () const override; + size_t memory_used () const override + { + size_t m = _data->size(); + if (_image) { + /* 3 components, 16-bits per pixel */ + m += 3 * 2 * _image->size().width * _image->size().height; + } + return m; + } private: - std::shared_ptr<const dcp::Data> _data; + Frame _data; dcp::Size _size; boost::optional<dcp::Eye> _eye; mutable std::shared_ptr<Image> _image; diff --git a/src/lib/map_cli.cc b/src/lib/map_cli.cc index be3841deb..aab4fabff 100644 --- a/src/lib/map_cli.cc +++ b/src/lib/map_cli.cc @@ -64,6 +64,7 @@ help(std::function<void (string)> out) 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 <uuid>.<mxf|xml>"); + out(" -k, --kdm decrypt assets using the given KDM"); out(" --config <dir> directory containing config.xml and cinemas.xml"); } @@ -77,6 +78,7 @@ map_cli(int argc, char* argv[], std::function<void (string)> out) bool rename = false; vector<boost::filesystem::path> assets_dir; optional<boost::filesystem::path> config_dir; + optional<boost::filesystem::path> kdm_filename; /* This makes it possible to call getopt several times in the same executable, for tests */ optind = 0; @@ -90,11 +92,12 @@ map_cli(int argc, char* argv[], std::function<void (string)> out) { "soft-link", no_argument, 0, 's' }, { "assets-dir", required_argument, 0, 'd' }, { "rename", no_argument, 0, 'r' }, + { "kdm", required_argument, 0, 'k' }, { "config", required_argument, 0, 'c' }, { 0, 0, 0, 0 } }; - int c = getopt_long(argc, argv, "ho:lsd:rc:", long_options, &option_index); + int c = getopt_long(argc, argv, "ho:lsd:rk:c:", long_options, &option_index); if (c == -1) { break; @@ -121,6 +124,9 @@ map_cli(int argc, char* argv[], std::function<void (string)> out) case 'r': rename = true; break; + case 'k': + kdm = optarg; + break; case 'c': config_dir = optarg; break; @@ -165,6 +171,19 @@ map_cli(int argc, char* argv[], std::function<void (string)> out) return String::compose("Could not create output directory %1: %2", *output_dir, ec.message()); } + optional<dcp::DecryptedKDM> kdm; + if (kdm_filename) { + auto key = Config::instance()->decryption_chain()->key(); + if (!key) { + return string{"Could not find private key to decrypt the KDM"}; + } + try { + kdm = dcp::DecryptedKDM(dcp::EncryptedKDM(dcp::file_to_string(*kdm_filename)), *key); + } catch (std::exception& e) { + return String::compose("Could not decrypt KDM %1: %2", kdm_filename, e.what()); + } + } + /* 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. diff --git a/src/lib/wscript b/src/lib/wscript index dfe3ce487..37ae1e19b 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -140,7 +140,6 @@ sources = """ image_proxy.cc image_store.cc internal_player_server.cc - j2k_image_proxy.cc job.cc job_manager.cc j2k_encoder.cc |
