Files
oak-editor/crates/oakaudio/src/synchronizer.rs
T
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00

98 lines
3.1 KiB
Rust

// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program 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 3 of the License, or
// (at your option) any later version.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
//! Timeline placement helpers (`olive::AudioSynchronizer`). All static in
//! C++; becomes a plain function module. Times are `core::Rational` (from
//! oakcore-rs).
use oakcore_rs::Rational;
use crate::params::rational_from_double;
/// One clip's source-time metadata.
pub struct SourceClip {
/// Source start time in seconds.
pub source_start_time: Rational,
/// Media in point in seconds.
pub media_in: Rational,
/// Whether `source_start_time` is set.
pub has_source_start_time: bool,
}
/// A timeline placement result.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Placement {
/// Candidate's timeline in point in seconds.
pub timeline_in: Rational,
/// Whether placement succeeded.
pub valid: bool,
}
/// Place the candidate on the timeline so its source time aligns with the
/// reference clip.
///
/// `// CPP-PARITY: src/audio/src/audiosynchronizer.cpp:27`
/// (`AudioSynchronizer::place_by_source_time`): invalid when either clip
/// lacks a source start time or carries a NaN rational.
pub fn place_by_source_time(
reference: &SourceClip,
candidate: &SourceClip,
reference_timeline_in: Rational,
) -> Placement {
let mut placement = Placement {
timeline_in: Rational::NULL,
valid: false,
};
if !reference.has_source_start_time
|| !candidate.has_source_start_time
|| reference.source_start_time.is_nan()
|| candidate.source_start_time.is_nan()
{
return placement;
}
let reference_head_source = reference.source_start_time + reference.media_in;
let candidate_head_source = candidate.source_start_time + candidate.media_in;
placement.timeline_in = reference_timeline_in + candidate_head_source - reference_head_source;
placement.valid = !placement.timeline_in.is_nan();
placement
}
/// Timeline placement from a measured waveform offset.
///
/// `// CPP-PARITY: src/audio/src/audiosynchronizer.cpp:50`
/// (`AudioSynchronizer::place_by_waveform_offset`): invalid for
/// `sample_rate <= 0`.
pub fn place_by_waveform_offset(
reference_timeline_in: Rational,
candidate_offset_samples: i64,
sample_rate: i32,
) -> Placement {
let mut placement = Placement {
timeline_in: Rational::NULL,
valid: false,
};
if sample_rate <= 0 {
return placement;
}
placement.timeline_in = reference_timeline_in
+ rational_from_double(candidate_offset_samples as f64 / f64::from(sample_rate));
placement.valid = !placement.timeline_in.is_nan();
placement
}