diff --git a/crates/oak-node/src/footage.rs b/crates/oak-node/src/footage.rs index 6b2971f83..c00dd6134 100644 --- a/crates/oak-node/src/footage.rs +++ b/crates/oak-node/src/footage.rs @@ -25,6 +25,13 @@ use crate::input::Input; use crate::node::{Category, NodeBehavior, NodeCore}; use crate::value::{AudioParams, NodeValue, NodeValueRow, NodeValueTable, ValueType, VideoParams}; +/// Whether the footage's proxy state value means "the proxy file is +/// ready on disk" (the C++ `ProxyState::Ready` = 2; the footage behavior +/// stores it raw to avoid a codec-crate dependency here). +pub(crate) fn proxymanager_proxy_state_ready(state: i32) -> bool { + state == 2 +} + /// One media stream inside a footage file. #[derive(Clone, Debug)] pub struct StreamInfo { @@ -364,9 +371,28 @@ impl NodeBehavior for FootageBehavior { let Some(stream) = self.streams.iter().find(|s| s.is_video) else { return; }; + // Proxy selection mirrors the montage path's + // `preview_footage_media`: enabled + Ready state, and the proxy + // replaces the first video stream only (C++ matches the proxy's + // video stream index against the footage's first video stream). + // `proxy_video_stream_index == -1` (a proxy generated before the + // stream index was recorded) still matches the FIRST video stream + // — a strict equality would silently disable the proxy and decode + // the 4K original again (the "preview stopped moving" report). + let proxy_ready = proxymanager_proxy_state_ready(self.proxy_state); + let use_proxy = self.proxy_enabled + && !self.proxy.is_empty() + && proxy_ready + && (self.proxy_video_stream_index == stream.index + || self.proxy_video_stream_index < 0); + let (filename, stream_index) = if use_proxy { + (self.proxy.clone(), 0) + } else { + (self.filename.clone(), stream.index) + }; let payload = crate::nodes::jobs::FootageJobPayload { - filename: self.filename.clone(), - stream_index: stream.index, + filename, + stream_index, time, }; table.push(