use super::diagnostics::{BoundaryAdjustment, BoundaryEdge, Diagnostics, TimelineWarning};
use super::{
MAX_ADJUSTMENTS, MAX_CLIPS, MAX_TRACKS, Timeline, Track, TrackBuilder, align, invalid, on_track,
};
use crate::ErrorReport;
use crate::authoring::timeline::soundtrack::Soundtrack;
use crate::{Color, Duration, Error, FrameRate, Size, TICKS_PER_SECOND, Time};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct TimelineBuilder {
size: Size,
rate: FrameRate,
background: Option<Color>,
tracks: Arc<Vec<Track>>,
track_end: Duration,
adjustments: Arc<Vec<BoundaryAdjustment>>,
soundtrack: Soundtrack,
nested_audio: Arc<Vec<crate::AudioClip>>,
duration: Option<Duration>,
errors: Arc<ErrorReport>,
}
impl TimelineBuilder {
pub(super) fn empty(size: Size, rate: FrameRate) -> Self {
Self {
size,
rate,
background: None,
tracks: Arc::new(Vec::new()),
track_end: Duration::ZERO,
adjustments: Arc::new(Vec::new()),
soundtrack: Soundtrack::default(),
nested_audio: Arc::default(),
duration: None,
errors: Arc::default(),
}
}
pub(super) fn from_timeline(timeline: &Timeline) -> Self {
Self {
size: timeline.size,
rate: timeline.rate,
background: timeline.background,
tracks: timeline.tracks.clone(),
track_end: timeline
.tracks
.iter()
.map(|track| track.duration)
.max()
.unwrap_or(Duration::ZERO),
adjustments: Arc::new(
timeline
.diagnostics
.boundary_adjustments()
.cloned()
.collect(),
),
soundtrack: timeline.soundtrack.clone(),
nested_audio: Arc::default(),
duration: (!timeline.inferred_duration).then_some(timeline.duration),
errors: Arc::default(),
}
}
pub(super) fn checked(size: Size, rate: FrameRate) -> crate::Result<Self> {
if !size.width.is_finite()
|| !size.height.is_finite()
|| size.width <= 0.0
|| size.height <= 0.0
{
return Err(invalid(
"Timeline design dimensions must be finite and positive",
));
}
if i128::from(rate.numerator())
> i128::from(TICKS_PER_SECOND) * i128::from(rate.denominator())
{
return Err(invalid("Frame rate exceeds one frame per tick"));
}
Ok(Self::empty(size, rate))
}
pub(super) fn failed(mut self, error: Error) -> Self {
Arc::make_mut(&mut self.errors).record(error);
self
}
fn defer(self, operation: impl FnOnce(Self) -> crate::Result<Self>) -> Self {
let mut previous = self.clone();
match operation(self) {
Ok(value) => value,
Err(error) => {
Arc::make_mut(&mut previous.errors).record(error);
previous
}
}
}
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
pub fn track(self, build: impl FnOnce(TrackBuilder) -> TrackBuilder) -> Self {
self.defer(|builder| builder.try_track(|track| Ok(build(track))))
}
pub fn try_track(
mut self,
build: impl FnOnce(TrackBuilder) -> crate::Result<TrackBuilder>,
) -> crate::Result<Self> {
if self.tracks.len() >= MAX_TRACKS {
return Err(invalid("Timeline exceeds 256 tracks"));
}
let builder = TrackBuilder {
index: self.tracks.len(),
rate: self.rate,
clips: Vec::new(),
cursor: Time::ZERO,
adjustments: Vec::new(),
pending: None,
errors: ErrorReport::default(),
audio: Vec::new(),
};
let builder = build(builder)?;
if builder.index != self.tracks.len() || builder.rate != self.rate {
return Err(invalid(
"Track builder belongs to a different index or frame grid",
));
}
let (track, adjustments, audio) = builder.finish()?;
let total = self
.tracks
.iter()
.map(|track| track.clips.len())
.sum::<usize>()
+ track.clips.len();
if total > MAX_CLIPS || self.adjustments.len() + adjustments.len() > MAX_ADJUSTMENTS {
return Err(invalid("Timeline exceeds clip or adjustment allowance"));
}
self.track_end = self.track_end.max(track.duration);
Arc::make_mut(&mut self.tracks).push(track);
Arc::make_mut(&mut self.adjustments).extend(adjustments);
Arc::make_mut(&mut self.nested_audio).extend(audio);
Ok(self)
}
pub fn audio(self, clip: crate::AudioClip) -> Self {
self.defer(|builder| builder.try_audio(clip))
}
pub fn try_audio(mut self, clip: crate::AudioClip) -> crate::Result<Self> {
if self.soundtrack.clips.len() >= 256 {
return Err(invalid("Timeline exceeds 256 audio clips"));
}
if !(clip.unresolved_file() && clip.parameters().duration.is_none()) {
self.soundtrack.clip_end(&clip, self.rate)?;
}
Arc::make_mut(&mut self.soundtrack.clips).push(clip);
Ok(self)
}
pub fn master(mut self, master: crate::audio::Master) -> Self {
self.soundtrack.master = master;
self
}
pub fn audio_bus(mut self, bus: crate::audio::AudioBus) -> Self {
Arc::make_mut(&mut self.soundtrack.buses).push(bus);
self
}
pub fn duration(self, duration: Duration) -> Self {
self.defer(|builder| builder.try_duration(duration))
}
pub fn try_duration(mut self, duration: Duration) -> crate::Result<Self> {
let (aligned, frame) = align(duration.after_origin(), self.rate)?;
if duration > Duration::ZERO && aligned == Time::ZERO {
return Err(invalid(
"Timeline duration becomes empty after frame alignment",
));
}
let adjustments = Arc::make_mut(&mut self.adjustments);
adjustments.retain(|adjustment| adjustment.edge != BoundaryEdge::Duration);
if duration != aligned.since_origin().magnitude() {
if adjustments.len() >= MAX_ADJUSTMENTS {
return Err(invalid("Timeline exceeds adjustment allowance"));
}
adjustments.push(BoundaryAdjustment {
track: None,
clip: None,
edge: BoundaryEdge::Duration,
requested: duration.after_origin(),
aligned,
frame,
});
}
self.duration = Some(aligned.since_origin().magnitude());
Ok(self)
}
pub fn build(self) -> Result<Timeline, ErrorReport> {
let mut errors = self.errors.as_ref().clone();
if let Err(error) = self.soundtrack.validate() {
errors.record(error);
}
for (track_index, track) in self.tracks.iter().enumerate() {
for clip in track.clips.iter() {
if let Err(error) = clip.scene.validate() {
errors.record(on_track(track_index, format!("clip[{}]: {error}", clip.id)));
}
}
}
let audio_end = match self.soundtrack.end(self.rate) {
Ok(end) => end,
Err(error) => {
errors.record(error);
Duration::ZERO
}
};
if !errors.is_empty() {
return Err(errors);
}
let mut soundtrack = self.soundtrack;
if !self.nested_audio.is_empty() {
let mut clips = soundtrack.clips.as_ref().clone();
clips.extend(self.nested_audio.iter().cloned());
if clips.len() > 256 {
return Err(invalid("Nested timeline exceeds 256 audio clips").into());
}
soundtrack.clips = Arc::new(clips);
}
let content = self.track_end.max(audio_end);
let mut warnings = self
.adjustments
.iter()
.cloned()
.map(TimelineWarning::Boundary)
.collect::<Vec<_>>();
if let Some(duration) = self.duration.filter(|duration| *duration < content) {
warnings.push(TimelineWarning::Truncated { duration, content });
}
Ok(Timeline {
size: self.size,
rate: self.rate,
background: self.background,
tracks: self.tracks,
duration: self.duration.unwrap_or(content),
inferred_duration: self.duration.is_none(),
diagnostics: Diagnostics::new(warnings),
soundtrack,
})
}
}