motion_core/authoring/timeline/diagnostics.rs
1//! Non-fatal observations recorded while building a timeline.
2
3use crate::{Duration, Time};
4use std::sync::Arc;
5
6/// Boundary that moved when scheduling onto the timeline's frame grid.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum BoundaryEdge {
9 /// Clip activation start.
10 Start,
11 /// Clip exclusive end.
12 End,
13 /// Trailing track cursor, including an explicit gap.
14 Cursor,
15 /// Explicit timeline duration override.
16 Duration,
17}
18
19/// Structured warning; construction never writes warnings to stderr.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct BoundaryAdjustment {
22 /// Track insertion index, absent for a timeline duration override.
23 pub track: Option<usize>,
24 /// Stable clip insertion ID, absent for cursors or duration overrides.
25 pub clip: Option<usize>,
26 /// Adjusted boundary category.
27 pub edge: BoundaryEdge,
28 /// Original tick position.
29 pub requested: Time,
30 /// Nearest representable frame boundary; ties choose the later frame.
31 pub aligned: Time,
32 /// Exact aligned frame index.
33 pub frame: i64,
34}
35
36/// One non-fatal observation about a built timeline.
37#[derive(Clone, Debug, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum TimelineWarning {
40 /// A boundary moved onto the frame grid.
41 Boundary(BoundaryAdjustment),
42 /// The explicit duration is shorter than the content, so the picture is cut
43 /// off. Audio is not trimmed; the output range limits it.
44 Truncated {
45 /// Explicit, frame-aligned duration.
46 duration: Duration,
47 /// Frame-aligned end of the tracks and audio.
48 content: Duration,
49 },
50}
51
52/// Warnings collected by a timeline builder; fatal errors are returned by `build`.
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54pub struct Diagnostics {
55 warnings: Arc<Vec<TimelineWarning>>,
56}
57
58impl Diagnostics {
59 pub(super) fn new(warnings: Vec<TimelineWarning>) -> Self {
60 Self {
61 warnings: Arc::new(warnings),
62 }
63 }
64
65 /// All warnings in the order they were recorded.
66 pub fn warnings(&self) -> &[TimelineWarning] {
67 &self.warnings
68 }
69
70 /// Frame-grid adjustments, without other warning kinds.
71 pub fn boundary_adjustments(&self) -> impl Iterator<Item = &BoundaryAdjustment> {
72 self.warnings.iter().filter_map(|warning| match warning {
73 TimelineWarning::Boundary(adjustment) => Some(adjustment),
74 _ => None,
75 })
76 }
77
78 /// Whether the timeline built without any warning.
79 pub fn is_empty(&self) -> bool {
80 self.warnings.is_empty()
81 }
82}