use tellur_core::geometry::Vec2;
use tellur_core::raster::{RasterImage, RasterResidency, Resolution};
use tellur_core::render_context::RenderContext;
use tellur_core::time::TimelineTime;
use tellur_core::timeline_component::{
resolve, resolve_with_canvas, Arrangement, AudioBuffer, ResolveError, ResolvedTimeline,
TimelineComponent,
};
#[doc(hidden)]
pub use tellur_core as __core;
pub const ENTRY_SYMBOL: &[u8] = b"tellur_timeline_collection_v9\0";
pub mod abi;
pub use abi::{
validate_plugin_fingerprint, AbiFingerprintFn, AbiMismatchError, ABI_FINGERPRINT,
ABI_FINGERPRINT_SYMBOL,
};
#[doc(hidden)]
#[macro_export]
macro_rules! __tellur_export_abi_fingerprint {
() => {
#[no_mangle]
pub extern "C" fn tellur_abi_fingerprint_v1() -> *const ::std::os::raw::c_char {
$crate::abi::ABI_FINGERPRINT_C.as_ptr().cast()
}
};
}
pub type EntryFn = unsafe extern "Rust" fn() -> Box<dyn TimelineCollection>;
#[derive(Debug, Clone, PartialEq)]
pub struct TimelineInfo {
pub id: String,
pub title: String,
pub duration: f64,
pub error: Option<String>,
}
pub trait TimelineCollection: Send {
fn timelines(&self) -> Vec<TimelineInfo>;
fn build(
&self,
id: &str,
t: TimelineTime,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage>;
fn arrangement(&self, _id: &str) -> Option<Arrangement> {
None
}
fn render_audio(&self, _id: &str, _rate: u32, _channels: u16) -> Option<AudioBuffer> {
None
}
fn render_audio_window(
&self,
_id: &str,
_start: f64,
_duration: f64,
_rate: u32,
_channels: u16,
) -> Option<AudioBuffer> {
None
}
}
pub struct SingleTimeline {
id: &'static str,
title: &'static str,
resolved: Result<ResolvedTimeline, ResolveError>,
}
pub fn single_timeline<T: TimelineComponent + Send + 'static>(
id: &'static str,
title: &'static str,
root: T,
) -> SingleTimeline {
SingleTimeline {
id,
title,
resolved: resolve(root),
}
}
pub fn single_timeline_with_canvas<T: TimelineComponent + Send + 'static>(
id: &'static str,
title: &'static str,
root: T,
canvas: Vec2,
) -> SingleTimeline {
SingleTimeline {
id,
title,
resolved: resolve_with_canvas(root, canvas),
}
}
impl SingleTimeline {
fn resolved(&self) -> Option<&ResolvedTimeline> {
self.resolved.as_ref().ok()
}
}
impl TimelineCollection for SingleTimeline {
fn timelines(&self) -> Vec<TimelineInfo> {
let (duration, error) = match &self.resolved {
Ok(resolved) => (resolved.duration(), None),
Err(e) => (0.0, Some(e.to_string())),
};
vec![TimelineInfo {
id: self.id.to_owned(),
title: self.title.to_owned(),
duration,
error,
}]
}
fn build(
&self,
id: &str,
t: TimelineTime,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
if id != self.id {
return None;
}
self.resolved()?.frame(t, target, residency, ctx)
}
fn arrangement(&self, id: &str) -> Option<Arrangement> {
if id != self.id {
return None;
}
Some(self.resolved()?.source().arrangement(0.0))
}
fn render_audio(&self, id: &str, rate: u32, channels: u16) -> Option<AudioBuffer> {
if id != self.id {
return None;
}
Some(self.resolved()?.render_audio(rate, channels))
}
fn render_audio_window(
&self,
id: &str,
start: f64,
duration: f64,
rate: u32,
channels: u16,
) -> Option<AudioBuffer> {
if id != self.id {
return None;
}
Some(
self.resolved()?
.render_audio_window(start, duration, rate, channels),
)
}
}
#[macro_export]
macro_rules! export_timeline {
(@__emit root = $root:expr, title = $title:expr, id = $id:expr) => {
$crate::__tellur_export_abi_fingerprint!();
#[no_mangle]
pub extern "Rust" fn tellur_timeline_collection_v9(
) -> ::std::boxed::Box<dyn $crate::TimelineCollection> {
let __root = $root;
::std::boxed::Box::new($crate::single_timeline($id, $title, __root))
}
};
(
@__emit
root = $root:expr,
title = $title:expr,
id = $id:expr,
canvas = ($w:expr, $h:expr)
) => {
$crate::__tellur_export_abi_fingerprint!();
#[no_mangle]
pub extern "Rust" fn tellur_timeline_collection_v9(
) -> ::std::boxed::Box<dyn $crate::TimelineCollection> {
let __root = $root;
::std::boxed::Box::new($crate::single_timeline_with_canvas(
$id,
$title,
__root,
$crate::__core::geometry::Vec2($w, $h),
))
}
};
(root = $root:expr, title = $title:expr $(,)?) => {
$crate::export_timeline!(@__emit root = $root, title = $title, id = "main");
};
(root = $root:expr, title = $title:expr, canvas = ($w:expr, $h:expr) $(,)?) => {
$crate::export_timeline!(
@__emit
root = $root,
title = $title,
id = "main",
canvas = ($w, $h)
);
};
(root = $root:expr, title = $title:expr, id = $id:expr $(,)?) => {
$crate::export_timeline!(@__emit root = $root, title = $title, id = $id);
};
(
root = $root:expr,
title = $title:expr,
id = $id:expr,
canvas = ($w:expr, $h:expr) $(,)?
) => {
$crate::export_timeline!(
@__emit
root = $root,
title = $title,
id = $id,
canvas = ($w, $h)
);
};
($id:expr, $title:expr, $builder:path $(,)?) => {
::core::compile_error!(
"export_timeline! now accepts `root = <TimelineComponent expression>, title = <title>`; call the former factory explicitly as `root = build()`"
);
};
($id:expr, $title:expr, $builder:path, canvas = ($w:expr, $h:expr) $(,)?) => {
::core::compile_error!(
"export_timeline! now accepts `root = <TimelineComponent expression>, title = <title>, canvas = (width, height)`; call the former factory explicitly as `root = build()`"
);
};
}
#[macro_export]
macro_rules! export_timeline_collection {
($builder:path) => {
$crate::__tellur_export_abi_fingerprint!();
#[no_mangle]
pub extern "Rust" fn tellur_timeline_collection_v9(
) -> ::std::boxed::Box<dyn $crate::TimelineCollection> {
::std::boxed::Box::new($builder())
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU8, Ordering};
use tellur_core::geometry::{Constraints, Vec2};
use tellur_core::raster::{PixelFormat, RasterComponent, RasterResidency};
use tellur_core::render_context::PassThrough;
use tellur_core::timeline_component::{Clock, NodeKind, Timed};
use tellur_core::timeline_container::Timeline as TimelineContainer;
#[derive(Clone, PartialEq, Hash)]
struct Dot;
impl RasterComponent for Dot {
fn layout(&self, _c: Constraints) -> Vec2 {
Vec2(1.0, 1.0)
}
fn render(
&self,
_s: Vec2,
_t: Resolution,
_residency: RasterResidency,
_ctx: &mut dyn RenderContext,
) -> RasterImage {
RasterImage::cpu(1, 1, PixelFormat::Rgba8, vec![0u8, 0, 0, 0])
}
}
fn build_new_api_timeline() -> TimelineContainer {
TimelineContainer::builder()
.child(Dot.at(0.0..2.0))
.child(Dot.at(0.0..3.0))
.build()
}
static LAST_COLLECTION_RESIDENCY: AtomicU8 = AtomicU8::new(0);
#[derive(Clone, PartialEq, Hash)]
struct ResidencyTimeline;
impl TimelineComponent for ResidencyTimeline {
fn duration(&self) -> Option<f64> {
Some(1.0)
}
fn frame(
&self,
_clock: Clock<'_>,
_canvas: Vec2,
_target: Resolution,
residency: RasterResidency,
_ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let value = match residency {
RasterResidency::Cpu => 1,
RasterResidency::Gpu => 2,
};
LAST_COLLECTION_RESIDENCY.store(value, Ordering::Relaxed);
Some(RasterImage::cpu(
1,
1,
PixelFormat::Rgba8,
vec![0u8, 0, 0, 0],
))
}
fn arrangement(&self, offset: f64) -> Arrangement {
Arrangement {
kind: NodeKind::Video,
label: String::new(),
name: None,
source: None,
start: offset,
end: offset + 1.0,
trim: None,
triggers: Vec::new(),
children: Vec::new(),
}
}
}
#[test]
fn entry_symbol_marks_the_cloneable_timeline_component_abi() {
assert_eq!(ENTRY_SYMBOL, b"tellur_timeline_collection_v9\0");
}
#[test]
fn single_timeline_forwards_residency() {
let collection = single_timeline("main", "Main", ResidencyTimeline);
let mut ctx = PassThrough;
let target = Resolution::new(1, 1);
LAST_COLLECTION_RESIDENCY.store(0, Ordering::Relaxed);
let _ = collection.build(
"main",
TimelineTime::new(0.0),
target,
RasterResidency::Gpu,
&mut ctx,
);
assert_eq!(LAST_COLLECTION_RESIDENCY.load(Ordering::Relaxed), 2);
}
#[test]
fn single_timeline_surfaces_resolved_duration() {
let collection = single_timeline("main", "Main", build_new_api_timeline());
let infos = collection.timelines();
assert_eq!(infos.len(), 1);
assert_eq!(infos[0].id, "main");
assert_eq!(infos[0].title, "Main");
assert_eq!(infos[0].duration, 3.0);
assert_eq!(infos[0].error, None);
}
#[test]
fn single_timeline_arrangement_walks_the_resolved_tree() {
let collection = single_timeline("main", "Main", build_new_api_timeline());
assert!(collection.arrangement("other").is_none());
let root = collection
.arrangement("main")
.expect("the resolved tree has an arrangement");
assert_eq!(root.kind, NodeKind::Timeline);
assert_eq!(root.start, 0.0);
assert_eq!(root.end, 3.0);
assert!(root.trim.is_none());
assert_eq!(root.children.len(), 2);
for child in &root.children {
assert_eq!(child.kind, NodeKind::Video);
assert!(child.children.is_empty());
}
}
}