use crate::audio;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, RasterResidency, Resolution};
use crate::render_context::RenderContext;
use crate::time::Time;
use crate::timeline_component::{
Arrangement, AudioBlockMut, AudioRenderContext, Clock, Cue, NodeKind, TimelineComponent, Trim,
TrimBounds,
};
pub(super) const STUB_PROBE_SECONDS: f64 = 1.0;
#[crate::component(timeline)]
#[derive(Clone, crate::Keyable)]
pub struct VideoFile {
#[builder(into)]
pub path: String,
#[builder(into)]
pub duration: Option<f64>,
}
impl VideoFile {
pub fn trim<R: TrimBounds>(self, bounds: R) -> Trim<Self> {
Trim::new(self, bounds)
}
fn probe(&self) -> f64 {
if let Some(d) = self.duration {
return d;
}
crate::video_decode::probe_duration(&self.path).unwrap_or(STUB_PROBE_SECONDS)
}
}
impl TimelineComponent for VideoFile {
fn duration(&self) -> Option<f64> {
Some(self.probe())
}
fn frame(
&self,
clock: Clock<'_>,
canvas: Vec2,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let _ = canvas;
let image =
crate::video_decode::decode_frame(&self.path, clock.local().seconds(), None, target)?;
Some(ctx.ensure_residency(image, residency))
}
fn arrangement(&self, offset: f64) -> Arrangement {
Arrangement {
kind: NodeKind::Video,
label: self.path.clone(),
name: None,
source: None,
start: offset,
end: offset + self.probe(),
trim: None,
triggers: Vec::new(),
children: Vec::new(),
}
}
}
#[crate::component(timeline)]
#[derive(Clone, crate::Keyable)]
pub struct AudioFile {
#[builder(into)]
pub path: String,
#[builder(default = 1.0)]
pub gain: f32,
#[builder(into)]
pub duration: Option<f64>,
}
impl AudioFile {
pub fn trim<R: TrimBounds>(self, bounds: R) -> Trim<Self> {
Trim::new(self, bounds)
}
fn probe(&self) -> f64 {
if let Some(d) = self.duration {
return d;
}
match audio::decoded_duration(&self.path, None) {
Ok(duration) => duration,
Err(_) => STUB_PROBE_SECONDS,
}
}
}
impl TimelineComponent for AudioFile {
fn duration(&self) -> Option<f64> {
Some(self.probe())
}
fn render_audio_block(&self, mut block: AudioBlockMut<'_>, ctx: &mut AudioRenderContext) {
let request = block.request();
block.clear();
let duration = self
.duration
.or_else(|| ctx.source_duration(&self.path))
.unwrap_or(STUB_PROBE_SECONDS);
if !request.may_overlap_local(0.0, duration) {
return;
}
let Some(buf) =
ctx.conformed_source(&self.path, request.rate(), request.channels(), self.gain)
else {
return;
};
let channels = request.channels() as usize;
let source_frames = buf.samples.len() / channels;
if source_frames == 0 {
return;
}
for output_frame in 0..request.frame_count() {
let local_time = request.time_at(output_frame);
if local_time < 0.0 || local_time >= duration {
continue;
}
let position = local_time * request.rate() as f64;
let source_frame = position.floor() as usize;
if source_frame >= source_frames {
continue;
}
let next_frame = (source_frame + 1).min(source_frames - 1);
let fraction = (position - source_frame as f64) as f32;
let output_base = output_frame * channels;
let source_base = source_frame * channels;
let next_base = next_frame * channels;
for channel in 0..channels {
let first = buf.samples[source_base + channel];
let second = buf.samples[next_base + channel];
block.samples_mut()[output_base + channel] = first + (second - first) * fraction;
}
}
}
fn arrangement(&self, offset: f64) -> Arrangement {
Arrangement {
kind: NodeKind::Audio,
label: self.path.clone(),
name: None,
source: None,
start: offset,
end: offset + self.probe(),
trim: None,
triggers: Vec::new(),
children: Vec::new(),
}
}
}
#[crate::component(timeline)]
#[derive(Clone, Copy, crate::Keyable)]
pub struct TimeBox {
pub duration: f64,
}
impl TimelineComponent for TimeBox {
fn duration(&self) -> Option<f64> {
Some(self.duration)
}
fn arrangement(&self, offset: f64) -> Arrangement {
Arrangement {
kind: NodeKind::Timeline,
label: "time box".to_owned(),
name: None,
source: None,
start: offset,
end: offset + self.duration,
trim: None,
triggers: Vec::new(),
children: Vec::new(),
}
}
}
#[crate::component(timeline)]
#[derive(Clone, crate::Keyable)]
pub struct Subtitle {
#[builder(into)]
pub text: String,
}
impl TimelineComponent for Subtitle {
fn cues(&self, offset: f64) -> Vec<Cue> {
let end = offset + self.duration().unwrap_or(0.0);
vec![Cue {
start: offset,
end,
text: self.text.clone(),
}]
}
fn arrangement(&self, offset: f64) -> Arrangement {
Arrangement {
kind: NodeKind::Subtitle,
label: self.text.clone(),
name: None,
source: None,
start: offset,
end: offset + self.duration().unwrap_or(0.0),
trim: None,
triggers: Vec::new(),
children: Vec::new(),
}
}
}