Files
oak-editor/crates/oakotio/tests/semantic.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

167 lines
4.9 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/>.
//! Semantic tests over `golden_timeline.json`: walk the parsed object graph
//! and assert the values Oak's C++ load task depends on.
use oakotio::{Clip, ExternalReference, MediaReference, Serializable, Timeline};
fn golden_timeline() -> Timeline {
let text = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/data/golden_timeline.json"
))
.expect("read golden_timeline.json");
match oakotio::from_json_string(&text).expect("parse golden_timeline.json") {
Serializable::Timeline(t) => t,
other => panic!("expected Timeline root, got {}", other.schema_name()),
}
}
#[test]
fn root_timeline_fields() {
let tl = golden_timeline();
assert_eq!(tl.name(), "My Sequence");
assert_eq!(tl.global_start_time(), None);
assert_eq!(tl.tracks().children().len(), 2);
}
#[test]
fn video_track_contents() {
let tl = golden_timeline();
let video = &tl.tracks().children()[0];
let track = video
.as_track()
.expect("first child of tracks stack is a Track");
assert_eq!(track.kind(), "Video");
assert_eq!(track.children().len(), 3);
// Clip -> Gap -> Transition, in order.
let clip = track.children()[0]
.as_clip()
.expect("video track child 0 is a Clip");
assert_eq!(clip.name(), "My Sequence Clip");
let range = clip.source_range().expect("clip has a source_range");
assert_eq!(
(range.duration().value(), range.duration().rate()),
(1152.0, 24.0)
);
assert_eq!(
(range.start_time().value(), range.start_time().rate()),
(0.0, 24.0)
);
let gap = track.children()[1]
.as_gap()
.expect("video track child 1 is a Gap");
assert_eq!(gap.name(), "My Sequence Gap");
let grange = gap.source_range().expect("gap has a source_range");
assert_eq!(
(grange.duration().value(), grange.duration().rate()),
(576.0, 24.0)
);
let trans = track.children()[2]
.as_transition()
.expect("video track child 2 is a Transition");
assert_eq!(trans.name(), "My Sequence Transition");
assert_eq!(
(trans.in_offset().value(), trans.in_offset().rate()),
(12.0, 24.0)
);
assert_eq!(
(trans.out_offset().value(), trans.out_offset().rate()),
(12.0, 24.0)
);
assert_eq!(trans.transition_type(), "");
}
#[test]
fn video_clip_media_reference() {
let tl = golden_timeline();
let clip = &tl.tracks().children()[0].as_track().unwrap().children()[0];
let clip = clip.as_clip().unwrap();
let reference = clip
.media_reference()
.expect("clip resolves a media reference");
assert_eq!(reference.schema_name(), "ExternalReference");
let external: &ExternalReference = match reference {
MediaReference::ExternalReference(e) => e,
other => panic!("expected ExternalReference, got {}", other.schema_name()),
};
assert_eq!(external.target_url(), "file:///tmp/My Sequence.mp4");
let available = external.available_range().expect("available_range is set");
assert_eq!(
(available.duration().value(), available.duration().rate()),
(100.0, 25.0)
);
assert_eq!(
(
available.start_time().value(),
available.start_time().rate()
),
(0.0, 25.0)
);
}
#[test]
fn audio_track_contents() {
let tl = golden_timeline();
let audio = &tl.tracks().children()[1];
let track = audio
.as_track()
.expect("second child of tracks stack is a Track");
assert_eq!(track.kind(), "Audio");
assert_eq!(track.children().len(), 2);
let clip: &Clip = track.children()[0]
.as_clip()
.expect("audio track child 0 is a Clip");
assert_eq!(clip.name(), "My Sequence Audio");
let external: &ExternalReference = match clip.media_reference().unwrap() {
MediaReference::ExternalReference(e) => e,
other => panic!("expected ExternalReference, got {}", other.schema_name()),
};
assert_eq!(external.target_url(), "file:///tmp/My Sequence.wav");
let available = external.available_range().unwrap();
assert_eq!(
(available.duration().value(), available.duration().rate()),
(0.0, 48000.0)
);
assert_eq!(
(
available.start_time().value(),
available.start_time().rate()
),
(0.0, 48000.0)
);
let gap = track.children()[1]
.as_gap()
.expect("audio track child 1 is a Gap");
assert_eq!(gap.name(), "");
let grange = gap.source_range().unwrap();
assert_eq!(
(grange.duration().value(), grange.duration().rate()),
(12.0, 1.0)
);
assert_eq!(
(grange.start_time().value(), grange.start_time().rate()),
(1152.0, 24.0)
);
}