1use alpha2_media::SourceId;
2use alpha2_model::{
3 ClipBlendMode, ClipEffect, ClipId, CompositionId, ProjectId, TrackId, TrackKind,
4 VisualProperties,
5};
6use alpha2_time::TimeRange;
7use std::fmt;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum EvalError {
13 InvalidProject(alpha2_model::ModelError),
15 UnmappableClip {
17 clip: ClipId,
19 },
20 IncompleteComposition {
22 clip: ClipId,
24 composition: CompositionId,
26 reason: crate::PlanError,
28 },
29 IndexProjectMismatch {
31 index_project: ProjectId,
33 evaluated_project: ProjectId,
35 },
36}
37
38impl std::error::Error for EvalError {
39 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40 match self {
41 Self::InvalidProject(error) => Some(error),
42 _ => None,
43 }
44 }
45}
46
47impl fmt::Display for EvalError {
48 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Self::InvalidProject(error) => {
51 write!(formatter, "cannot index invalid project: {error}")
52 }
53 Self::UnmappableClip { clip } => {
54 write!(
55 formatter,
56 "clip {clip} cannot be mapped to a source timestamp"
57 )
58 }
59 Self::IncompleteComposition {
60 clip,
61 composition,
62 reason,
63 } => match reason {
64 crate::PlanError::NoOutput => write!(
65 formatter,
66 "composition {composition} of clip {clip} has no output"
67 ),
68 crate::PlanError::MissingInput { node, input } => write!(
69 formatter,
70 "input {input} of node {node} in composition {composition} of clip {clip} is not connected"
71 ),
72 crate::PlanError::UnboundGroupInput { node } => write!(
73 formatter,
74 "group input {node} in composition {composition} of clip {clip} is fed by no group node"
75 ),
76 crate::PlanError::ClipWithoutClip { node } => write!(
77 formatter,
78 "clip node {node} in composition {composition}, drawn as clip {clip}'s source, shows no clip"
79 ),
80 crate::PlanError::NestedTooDeep { node } => write!(
81 formatter,
82 "node {node} in composition {composition} of clip {clip} nests compositions more than {} deep",
83 alpha2_model::MAX_COMPOSITION_DEPTH
84 ),
85 crate::PlanError::TooLarge => write!(
86 formatter,
87 "composition {composition} of clip {clip} draws more than {} nodes, or the project's compositions hold more than {} bytes once inlined",
88 alpha2_model::MAX_INLINED_NODES,
89 crate::MAX_PLAN_BYTES
90 ),
91 },
92 Self::IndexProjectMismatch {
93 index_project,
94 evaluated_project,
95 } => {
96 write!(
97 formatter,
98 "index belongs to project {index_project}, not {evaluated_project}"
99 )
100 }
101 }
102 }
103}
104
105#[derive(Clone, Debug)]
106pub(crate) struct IndexedClip {
107 pub(crate) clip_id: ClipId,
108 pub(crate) source_id: SourceId,
109 pub(crate) track_id: TrackId,
110 pub(crate) track_kind: TrackKind,
111 pub(crate) timeline_range: TimeRange,
112 pub(crate) source_range: TimeRange,
113 pub(crate) time_map: alpha2_model::TimeMap,
114 pub(crate) visual: VisualProperties,
115 pub(crate) composition: Option<crate::IndexedComposition>,
116 pub(crate) blend_mode: ClipBlendMode,
117 pub(crate) cache: alpha2_model::CacheIntent,
118 pub(crate) masks: Vec<alpha2_model::ShapeMask>,
119 pub(crate) matte: Option<alpha2_model::Matte>,
120 pub(crate) is_matte: bool,
122 pub(crate) audio: alpha2_model::AudioProperties,
123 pub(crate) source_is_audible: bool,
124 pub(crate) generator: Option<alpha2_model::Generator>,
125 pub(crate) plan: Option<std::sync::Arc<crate::GeneratorPlan>>,
127 pub(crate) source_composition: Option<(alpha2_model::CompositionId, crate::index::PlanSlot)>,
129 pub(crate) track_order: usize,
130 pub(crate) clip_order: usize,
131}
132
133#[derive(Clone, Debug)]
134pub(crate) struct IndexedTransition {
135 pub(crate) from_clip: ClipId,
136 pub(crate) to_clip: ClipId,
137 pub(crate) timeline_range: TimeRange,
138 pub(crate) easing: alpha2_model::Easing,
139 pub(crate) effect: ClipEffect,
140 pub(crate) track_order: usize,
141}
142
143#[derive(Clone, Debug)]
144pub(crate) struct IndexedCaptions {
145 pub(crate) track_id: TrackId,
146 pub(crate) track_order: usize,
147 pub(crate) captions: alpha2_model::Captions,
148 pub(crate) styles: Vec<alpha2_model::TextStyle>,
150 pub(crate) cue_styles: Vec<usize>,
152}
153
154impl IndexedCaptions {
155 pub(crate) fn new(
156 track_id: TrackId,
157 track_order: usize,
158 captions: alpha2_model::Captions,
159 ) -> Self {
160 let mut styles = vec![captions.style.clone()];
161 let mut by_name = std::collections::BTreeMap::<&str, usize>::new();
162 let mut cue_styles = Vec::with_capacity(captions.cues.len());
163 for cue in &captions.cues {
164 let name = cue.style.as_deref().or_else(|| {
166 cue.speaker
167 .as_deref()
168 .filter(|speaker| captions.styles.contains_key(*speaker))
169 });
170 let index = match name.and_then(|name| captions.styles.get_key_value(name)) {
171 Some((name, patch)) => *by_name.entry(name.as_str()).or_insert_with(|| {
172 styles.push(patch.apply(&captions.style));
173 styles.len() - 1
174 }),
175 None => 0,
176 };
177 cue_styles.push(index);
178 }
179 Self {
180 track_id,
181 track_order,
182 captions,
183 styles,
184 cue_styles,
185 }
186 }
187}