diff options
| author | Carl Hetherington <cth@carlh.net> | 2026-02-08 00:52:27 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2026-02-12 21:01:59 +0100 |
| commit | 7b19d27983cb8078e8b35407a25e62da4ec687fe (patch) | |
| tree | 1d038a938e81eb452dc3c8c970d3e54edd307734 | |
| parent | 55c53dd8621a30d3c39858216da6b5d2e6445fb0 (diff) | |
Re-use assets verbatim if they do not need to change (#448).
| -rw-r--r-- | src/lib/dcp_decoder.cc | 17 | ||||
| -rw-r--r-- | src/lib/dcp_decoder.h | 4 | ||||
| -rw-r--r-- | src/lib/player.cc | 4 | ||||
| -rw-r--r-- | src/lib/reel_writer.cc | 15 | ||||
| -rw-r--r-- | src/lib/reusable_reel_asset.cc | 31 | ||||
| -rw-r--r-- | test/reuse_test.cc | 49 | ||||
| -rw-r--r-- | test/wscript | 1 |
7 files changed, 99 insertions, 22 deletions
diff --git a/src/lib/dcp_decoder.cc b/src/lib/dcp_decoder.cc index 27d0f3a94..59dd7c008 100644 --- a/src/lib/dcp_decoder.cc +++ b/src/lib/dcp_decoder.cc @@ -28,6 +28,7 @@ #include "dcp_decoder.h" #include "dcpomatic_log.h" #include "digester.h" +#include "film.h" #include "ffmpeg_image_proxy.h" #include "frame_interval_checker.h" #include "image.h" @@ -146,6 +147,12 @@ DCPDecoder::DCPDecoder(shared_ptr<const Film> film, shared_ptr<const DCPContent> _font_id_allocator.add_fonts_from_reels(_reels); _font_id_allocator.allocate(); + + _can_reuse_video = _dcp_content->can_reuse_video(film) && !film->reencode_j2k(); + _can_reuse_audio = _dcp_content->can_reuse_audio(film); + for (int i = 0; i < static_cast<int>(TextType::COUNT); ++i) { + _can_reuse_text[i] = _dcp_content->can_reuse_text(film, static_cast<TextType>(i)); + } } @@ -176,7 +183,7 @@ DCPDecoder::pass() */ pass_texts(_next, picture_asset->size()); - if ((_j2k_mono_reader || _j2k_stereo_reader || _mpeg2_mono_reader) && (_decode_reusable || !_dcp_content->reference_video())) { + if ((_j2k_mono_reader || _j2k_stereo_reader || _mpeg2_mono_reader) && (_decode_reusable || !_can_reuse_video)) { auto const entry_point = (*_reel)->main_picture()->entry_point().get_value_or(0); if (_j2k_mono_reader) { video->emit( @@ -233,7 +240,7 @@ DCPDecoder::pass() } } - if (_sound_reader && (_decode_reusable || !_dcp_content->reference_audio())) { + if (_sound_reader && (_decode_reusable || !_can_reuse_audio)) { auto const entry_point = (*_reel)->main_sound()->entry_point().get_value_or(0); auto sf = _sound_reader->get_frame(entry_point + frame); auto from = sf->data(); @@ -331,7 +338,7 @@ DCPDecoder::pass_texts( auto const asset = reel_asset->asset(); auto const entry_point = reel_asset->entry_point().get_value_or(0); - if (_decode_reusable || !_dcp_content->reference_text(type)) { + if (_decode_reusable || !_can_reuse_text[type]) { auto subs = asset->texts_during( dcp::Time(entry_point + frame, vfr, vfr), dcp::Time(entry_point + frame + 1, vfr, vfr), @@ -522,10 +529,10 @@ DCPDecoder::set_decode_reusable(bool r) _decode_reusable = r; if (video) { - video->set_ignore(_dcp_content->reference_video() && !_decode_reusable); + video->set_ignore(_can_reuse_video && !_decode_reusable); } if (audio) { - audio->set_ignore(_dcp_content->reference_audio() && !_decode_reusable); + audio->set_ignore(_can_reuse_audio && !_decode_reusable); } } diff --git a/src/lib/dcp_decoder.h b/src/lib/dcp_decoder.h index dc30526ff..01b85b8d1 100644 --- a/src/lib/dcp_decoder.h +++ b/src/lib/dcp_decoder.h @@ -116,4 +116,8 @@ private: std::string _lazy_digest; FontIDAllocator _font_id_allocator; + + bool _can_reuse_video; + bool _can_reuse_audio; + EnumIndexedVector<bool, TextType> _can_reuse_text; }; diff --git a/src/lib/player.cc b/src/lib/player.cc index 86f6e1082..316cf7e2b 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -758,10 +758,10 @@ Player::pass() earliest_content->done = earliest_content->decoder->pass(); auto dcp = dynamic_pointer_cast<DCPContent>(earliest_content->content); if (dcp && !_play_reusable) { - if (dcp->reference_video()) { + if (dcp->can_reuse_video(film)) { _next_video_time = dcp->end(film); } - if (dcp->reference_audio()) { + if (dcp->can_reuse_audio(film)) { /* We are skipping some referenced DCP audio content, so we need to update _next_audio_time to `hide' the fact that no audio was emitted during the referenced DCP (though we need to behave as though it was). diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc index 67e707d52..2c4fac6e0 100644 --- a/src/lib/reel_writer.cc +++ b/src/lib/reel_writer.cc @@ -411,7 +411,7 @@ ReelWriter::finish(boost::filesystem::path output_dcp) template <class Asset> shared_ptr<Asset> -maybe_reuse_asset(char const* log_name, list<ReusableReelAsset> const& refs, DCPTimePeriod period) +maybe_reuse_asset(char const* log_name, list<ReusableReelAsset> const& refs, DCPTimePeriod period, boost::filesystem::path output_dir) { shared_ptr<Asset> reusable; @@ -423,6 +423,13 @@ maybe_reuse_asset(char const* log_name, list<ReusableReelAsset> const& refs, DCP } if (asset && ref.period == period) { reusable = asset; + if (ref.use == ReusableReelAsset::Use::COPY) { + auto mxf = reusable->asset_ref().asset(); + DCPOMATIC_ASSERT(mxf->file()); + auto const destination = output_dir / mxf->file()->filename(); + copy_file(*mxf->file(), destination); + mxf->set_file(destination); + } /* If we have a hash for this asset in the CPL, assume that it is correct */ if (reusable->hash()) { reusable->asset_ref()->set_hash(reusable->hash().get()); @@ -484,7 +491,7 @@ ReelWriter::maybe_add_text( } else { /* We don't have a subtitle asset of our own; hopefully we have one to reuse */ - reel_asset = maybe_reuse_asset<dcp::ReelTextAsset>("subtitle", refs, _period); + reel_asset = maybe_reuse_asset<dcp::ReelTextAsset>("subtitle", refs, _period, _output_dir); } if (reel_asset) { @@ -519,7 +526,7 @@ ReelWriter::create_reel_picture(shared_ptr<dcp::Reel> reel, list<ReusableReelAss reel_asset = make_shared<dcp::ReelMonoPictureAsset>(_mpeg2_picture_asset, 0); } else { /* We don't have a picture asset of our own; hopefully we have one to reuse */ - reel_asset = maybe_reuse_asset<dcp::ReelPictureAsset>("picture", refs, _period); + reel_asset = maybe_reuse_asset<dcp::ReelPictureAsset>("picture", refs, _period, _output_dir); } Frame const period_duration = _period.duration().frames_round(film()->video_frame_rate()); @@ -552,7 +559,7 @@ ReelWriter::create_reel_sound(shared_ptr<dcp::Reel> reel, list<ReusableReelAsset reel_asset = make_shared<dcp::ReelSoundAsset>(_sound_asset, 0); } else { /* We don't have a sound asset of our own; hopefully we have one to reuse */ - reel_asset = maybe_reuse_asset<dcp::ReelSoundAsset>("sound", refs, _period); + reel_asset = maybe_reuse_asset<dcp::ReelSoundAsset>("sound", refs, _period, _output_dir); } auto const period_duration = _period.duration().frames_round(film()->video_frame_rate()); diff --git a/src/lib/reusable_reel_asset.cc b/src/lib/reusable_reel_asset.cc index ab14460e0..3894cc4e0 100644 --- a/src/lib/reusable_reel_asset.cc +++ b/src/lib/reusable_reel_asset.cc @@ -50,8 +50,12 @@ maybe_add_asset(list<ReusableReelAsset>& a, shared_ptr<dcp::ReelFileAsset> r, Fr r->set_duration (r->actual_duration() - reel_trim_start - reel_trim_end); if (r->actual_duration() > 0) { a.push_back ( - ReusableReelAsset(r, DCPTimePeriod(from, from + DCPTime::from_frames(r->actual_duration(), ffr)), ReusableReelAsset::Use::REFERENCE) - ); + ReusableReelAsset( + r, + DCPTimePeriod(from, from + DCPTime::from_frames(r->actual_duration(), ffr)), + reference ? ReusableReelAsset::Use::REFERENCE : ReusableReelAsset::Use::COPY + ) + ); } } @@ -69,7 +73,12 @@ get_reusable_reel_assets(shared_ptr<const Film> film, shared_ptr<const Playlist> continue; } - if (!dcp->reference_video() && !dcp->reference_audio() && !dcp->reference_text(TextType::OPEN_SUBTITLE) && !dcp->reference_text(TextType::CLOSED_CAPTION)) { + if ( + !dcp->can_reuse_video(film) && + !dcp->can_reuse_audio(film) && + !dcp->can_reuse_text(film, TextType::OPEN_SUBTITLE) && + !dcp->can_reuse_text(film, TextType::CLOSED_CAPTION) + ) { continue; } @@ -107,21 +116,21 @@ get_reusable_reel_assets(shared_ptr<const Film> film, shared_ptr<const Playlist> Frame const reel_trim_end = min(reel_duration, max(int64_t(0), reel_duration - (offset_from_end - trim_end))); auto const from = content->position() + std::max(DCPTime(), DCPTime::from_frames(offset_from_start - trim_start, frame_rate)); - if (dcp->reference_video()) { - maybe_add_asset (reel_assets, reel->main_picture(), reel_trim_start, reel_trim_end, from, frame_rate); + if (dcp->can_reuse_video(film)) { + maybe_add_asset(reel_assets, reel->main_picture(), reel_trim_start, reel_trim_end, from, frame_rate, dcp->reference_video()); } - if (dcp->reference_audio()) { - maybe_add_asset (reel_assets, reel->main_sound(), reel_trim_start, reel_trim_end, from, frame_rate); + if (dcp->can_reuse_audio(film)) { + maybe_add_asset(reel_assets, reel->main_sound(), reel_trim_start, reel_trim_end, from, frame_rate, dcp->reference_audio()); } - if (dcp->reference_text(TextType::OPEN_SUBTITLE) && reel->main_subtitle()) { - maybe_add_asset (reel_assets, reel->main_subtitle(), reel_trim_start, reel_trim_end, from, frame_rate); + if (dcp->can_reuse_text(film, TextType::OPEN_SUBTITLE) && reel->main_subtitle()) { + maybe_add_asset(reel_assets, reel->main_subtitle(), reel_trim_start, reel_trim_end, from, frame_rate, dcp->reference_text(TextType::OPEN_SUBTITLE)); } - if (dcp->reference_text(TextType::CLOSED_CAPTION)) { + if (dcp->can_reuse_text(film, TextType::CLOSED_CAPTION)) { for (auto caption: reel->closed_captions()) { - maybe_add_asset (reel_assets, caption, reel_trim_start, reel_trim_end, from, frame_rate); + maybe_add_asset(reel_assets, caption, reel_trim_start, reel_trim_end, from, frame_rate, dcp->reference_text(TextType::CLOSED_CAPTION)); } } diff --git a/test/reuse_test.cc b/test/reuse_test.cc new file mode 100644 index 000000000..54d98abe9 --- /dev/null +++ b/test/reuse_test.cc @@ -0,0 +1,49 @@ +/* + Copyright (C) 2026 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/dcp_content.h" +#include "lib/film.h" +#include "lib/ratio.h" +#include "test.h" +#include <boost/test/unit_test.hpp> + + +using std::make_shared; + + +BOOST_AUTO_TEST_CASE(reuse_whole_dcp_test) +{ + boost::filesystem::path const source = "test/data/scaling_test_133_185"; + auto dcp = make_shared<DCPContent>(source); + auto film = new_test_film("reuse_basic_test", { dcp }); + film->set_container(Ratio::from_id("185")); + film->set_audio_channels(8); + make_and_verify_dcp( + film, + { + dcp::VerificationNote::Code::INVALID_STANDARD, + }); + + auto const output = film->dir(film->dcp_name()); + check_file(source / "j2c_97b50c93-ad3a-45d7-8ce0-4b875302a704.mxf", output / "j2c_97b50c93-ad3a-45d7-8ce0-4b875302a704.mxf"); + check_file(source / "pcm_156782ba-4cae-4468-b3ea-1126be23b6d6.mxf", output / "pcm_156782ba-4cae-4468-b3ea-1126be23b6d6.mxf"); +} + diff --git a/test/wscript b/test/wscript index a4a96c381..d16717704 100644 --- a/test/wscript +++ b/test/wscript @@ -158,6 +158,7 @@ def build(bld): remake_video_test.cc remake_with_subtitle_test.cc render_subtitles_test.cc + reuse_test.cc scaling_test.cc scoped_temporary_test.cc silence_padding_test.cc |
