1use schemars::JsonSchema;
39use serde::{Deserialize, Serialize};
40
41use crate::{IdMetadataExt, Item, Seconds, Stack, TimeRange, Timeline, Track};
42
43pub const MARKER_CREATED_BY_KEY: &str = "created_by";
45
46pub const DEFAULT_MARKER_COLOR: &str = "RED";
48
49pub const MARKER_COLORS: [&str; 11] = [
53 "PINK", "RED", "ORANGE", "YELLOW", "GREEN", "CYAN", "BLUE", "PURPLE", "MAGENTA", "BLACK",
54 "WHITE",
55];
56
57fn default_marker_schema() -> String {
58 "Marker.2".to_string()
59}
60
61fn default_marker_metadata() -> serde_json::Value {
64 crate::types::ensure_tellers_ai_with_id(serde_json::Value::Null)
65}
66
67fn default_marker_color() -> String {
68 DEFAULT_MARKER_COLOR.to_string()
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
72pub struct Marker {
73 #[serde(rename = "OTIO_SCHEMA", default = "default_marker_schema")]
74 pub otio_schema: String,
75 #[serde(default)]
76 pub name: String,
77 #[serde(default)]
78 pub comment: String,
79 #[serde(default = "default_marker_color")]
80 pub color: String,
81 #[serde(default)]
82 pub marked_range: TimeRange,
83 #[serde(
84 default = "default_marker_metadata",
85 deserialize_with = "crate::types::deserialize_metadata_with_id"
86 )]
87 pub metadata: serde_json::Value,
88}
89
90impl Default for Marker {
91 fn default() -> Self {
92 Marker::new(0.0, 0.0, "", "", None, None, None)
93 }
94}
95
96impl Marker {
97 pub fn new(
101 start: Seconds,
102 duration: Seconds,
103 name: impl Into<String>,
104 comment: impl Into<String>,
105 color: Option<String>,
106 created_by: Option<String>,
107 id: Option<String>,
108 ) -> Self {
109 let mut m = Marker {
110 otio_schema: default_marker_schema(),
111 name: name.into(),
112 comment: comment.into(),
113 color: color.unwrap_or_else(default_marker_color),
114 marked_range: TimeRange::new(duration.max(0.0), start),
115 metadata: serde_json::Value::Object(serde_json::Map::new()),
116 };
117 m.set_id(Some(id.unwrap_or_else(crate::types::gen_hex_id_12)));
118 m.set_created_by(created_by);
119 m
120 }
121
122 pub fn start(&self) -> Seconds {
123 self.marked_range.get_start_time()
124 }
125
126 pub fn duration(&self) -> Seconds {
127 self.marked_range.get_duration().max(0.0)
128 }
129
130 pub fn get_created_by(&self) -> Option<String> {
132 self.metadata
133 .get("tellers.ai")
134 .and_then(|v| v.get(MARKER_CREATED_BY_KEY))
135 .and_then(|v| v.as_str())
136 .map(|s| s.to_string())
137 }
138
139 pub fn set_created_by(&mut self, user_id: Option<String>) {
140 if self.metadata.as_object().is_none() {
141 self.metadata = serde_json::Value::Object(serde_json::Map::new());
142 }
143 let ai = self
144 .metadata
145 .as_object_mut()
146 .unwrap()
147 .entry("tellers.ai".to_string())
148 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
149 if ai.as_object().is_none() {
150 *ai = serde_json::Value::Object(serde_json::Map::new());
151 }
152 let ai = ai.as_object_mut().unwrap();
153 match user_id {
154 Some(id) => {
155 ai.insert(
156 MARKER_CREATED_BY_KEY.to_string(),
157 serde_json::Value::String(id),
158 );
159 }
160 None => {
161 ai.remove(MARKER_CREATED_BY_KEY);
162 }
163 }
164 }
165}
166
167impl IdMetadataExt for Marker {
168 fn get_id(&self) -> Option<String> {
169 self.metadata
170 .get("tellers.ai")
171 .and_then(|v| v.get("timeline_id"))
172 .and_then(|v| v.as_str())
173 .map(|s| s.to_string())
174 }
175 fn set_id(&mut self, id: Option<String>) {
176 crate::metadata::write_id_to_metadata(&mut self.metadata, id)
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
182#[serde(rename_all = "snake_case")]
183pub enum MarkerOwnerKind {
184 Stack,
185 Track,
186 Clip,
187 Gap,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
192pub struct MarkerInfo {
193 pub marker: Marker,
194 pub owner_kind: MarkerOwnerKind,
195 pub owner_id: Option<String>,
197 pub track_id: Option<String>,
200 pub timeline_start: Seconds,
202 pub duration: Seconds,
204 pub visible: bool,
207}
208
209#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
211pub struct MarkerUpdate {
212 pub name: Option<String>,
213 pub comment: Option<String>,
214 pub color: Option<String>,
215 pub timeline_start: Option<Seconds>,
217 pub duration: Option<Seconds>,
218}
219
220fn item_source_to_timeline_offset(track: &Track, index: usize) -> Seconds {
222 let item = &track.items[index];
223 track.start_time_of_item(index) - item.get_source_range().get_start_time()
224}
225
226fn item_markers(item: &Item) -> &Vec<Marker> {
227 match item {
228 Item::Clip(c) => &c.markers,
229 Item::Gap(g) => &g.markers,
230 }
231}
232
233fn item_markers_mut(item: &mut Item) -> &mut Vec<Marker> {
234 match item {
235 Item::Clip(c) => &mut c.markers,
236 Item::Gap(g) => &mut g.markers,
237 }
238}
239
240fn find_marker_index(markers: &[Marker], marker_id: &str) -> Option<usize> {
241 markers
242 .iter()
243 .position(|m| m.get_id().as_deref() == Some(marker_id))
244}
245
246enum MarkerLocation {
248 Stack(usize),
249 Track(usize, usize),
250 Item(usize, usize, usize, Seconds),
251}
252
253impl Stack {
254 pub fn get_markers(&self) -> Vec<MarkerInfo> {
257 let mut out = Vec::new();
258 for m in &self.markers {
259 out.push(MarkerInfo {
260 marker: m.clone(),
261 owner_kind: MarkerOwnerKind::Stack,
262 owner_id: None,
263 track_id: None,
264 timeline_start: m.start(),
265 duration: m.duration(),
266 visible: true,
267 });
268 }
269 for track in &self.children {
270 let track_id = track.get_id();
271 for m in &track.markers {
272 out.push(MarkerInfo {
273 marker: m.clone(),
274 owner_kind: MarkerOwnerKind::Track,
275 owner_id: track_id.clone(),
276 track_id: track_id.clone(),
277 timeline_start: m.start(),
278 duration: m.duration(),
279 visible: true,
280 });
281 }
282 for (ii, item) in track.items.iter().enumerate() {
283 let markers = item_markers(item);
284 if markers.is_empty() {
285 continue;
286 }
287 let offset = item_source_to_timeline_offset(track, ii);
288 let range = item.get_source_range();
289 let (src_start, src_end) = (
290 range.get_start_time(),
291 range.get_start_time() + range.get_duration(),
292 );
293 let owner_kind = match item {
294 Item::Clip(_) => MarkerOwnerKind::Clip,
295 Item::Gap(_) => MarkerOwnerKind::Gap,
296 };
297 for m in markers {
298 const EPS: Seconds = 1e-9;
299 out.push(MarkerInfo {
300 marker: m.clone(),
301 owner_kind,
302 owner_id: item.get_id(),
303 track_id: track_id.clone(),
304 timeline_start: m.start() + offset,
305 duration: m.duration(),
306 visible: m.start() >= src_start - EPS && m.start() <= src_end + EPS,
307 });
308 }
309 }
310 }
311 out
312 }
313
314 pub fn get_marker(&self, marker_id: &str) -> Option<MarkerInfo> {
316 self.get_markers()
317 .into_iter()
318 .find(|info| info.marker.get_id().as_deref() == Some(marker_id))
319 }
320
321 fn locate_marker(&self, marker_id: &str) -> Option<MarkerLocation> {
322 if let Some(mi) = find_marker_index(&self.markers, marker_id) {
323 return Some(MarkerLocation::Stack(mi));
324 }
325 for (ti, track) in self.children.iter().enumerate() {
326 if let Some(mi) = find_marker_index(&track.markers, marker_id) {
327 return Some(MarkerLocation::Track(ti, mi));
328 }
329 for (ii, item) in track.items.iter().enumerate() {
330 if let Some(mi) = find_marker_index(item_markers(item), marker_id) {
331 let offset = item_source_to_timeline_offset(track, ii);
332 return Some(MarkerLocation::Item(ti, ii, mi, offset));
333 }
334 }
335 }
336 None
337 }
338
339 fn marker_mut(&mut self, location: &MarkerLocation) -> (&mut Marker, Seconds) {
340 match *location {
341 MarkerLocation::Stack(mi) => (&mut self.markers[mi], 0.0),
342 MarkerLocation::Track(ti, mi) => (&mut self.children[ti].markers[mi], 0.0),
343 MarkerLocation::Item(ti, ii, mi, offset) => (
344 &mut item_markers_mut(&mut self.children[ti].items[ii])[mi],
345 offset,
346 ),
347 }
348 }
349
350 pub fn add_marker(&mut self, owner_id: Option<&str>, mut marker: Marker) -> Option<String> {
358 let marker_id = match marker.get_id() {
359 Some(id) if !id.is_empty() => id,
360 _ => {
361 let id = crate::types::gen_hex_id_12();
362 marker.set_id(Some(id.clone()));
363 id
364 }
365 };
366 if self.locate_marker(&marker_id).is_some() {
367 return None;
368 }
369 let timeline_start = marker.start();
370 let stack_id = self.metadata.get("tellers.ai")
371 .and_then(|v| v.get("timeline_id"))
372 .and_then(|v| v.as_str())
373 .map(|s| s.to_string());
374 match owner_id {
375 None => {
376 self.markers.push(marker);
377 return Some(marker_id);
378 }
379 Some(id) if stack_id.as_deref() == Some(id) => {
380 self.markers.push(marker);
381 return Some(marker_id);
382 }
383 Some(id) => {
384 if let Some(ti) = self
385 .children
386 .iter()
387 .position(|t| t.get_id().as_deref() == Some(id))
388 {
389 self.children[ti].markers.push(marker);
390 return Some(marker_id);
391 }
392 for track in &mut self.children {
393 let Some(ii) = track
394 .items
395 .iter()
396 .position(|it| it.get_id().as_deref() == Some(id))
397 else {
398 continue;
399 };
400 let offset = item_source_to_timeline_offset(track, ii);
401 let Item::Clip(clip) = &mut track.items[ii] else {
402 return None;
403 };
404 marker
405 .marked_range
406 .set_start_time(timeline_start - offset);
407 clip.markers.push(marker);
408 return Some(marker_id);
409 }
410 None
411 }
412 }
413 }
414
415 pub fn update_marker(&mut self, marker_id: &str, update: MarkerUpdate) -> bool {
418 let Some(location) = self.locate_marker(marker_id) else {
419 return false;
420 };
421 let (marker, offset) = self.marker_mut(&location);
422 if let Some(name) = update.name {
423 marker.name = name;
424 }
425 if let Some(comment) = update.comment {
426 marker.comment = comment;
427 }
428 if let Some(color) = update.color {
429 marker.color = color;
430 }
431 if let Some(start) = update.timeline_start {
432 marker.marked_range.set_start_time(start - offset);
433 }
434 if let Some(duration) = update.duration {
435 marker.marked_range.set_duration(duration.max(0.0));
436 }
437 true
438 }
439
440 pub fn delete_marker(&mut self, marker_id: &str) -> Option<Marker> {
443 Some(match self.locate_marker(marker_id)? {
444 MarkerLocation::Stack(mi) => self.markers.remove(mi),
445 MarkerLocation::Track(ti, mi) => self.children[ti].markers.remove(mi),
446 MarkerLocation::Item(ti, ii, mi, _) => {
447 item_markers_mut(&mut self.children[ti].items[ii]).remove(mi)
448 }
449 })
450 }
451}
452
453impl Timeline {
454 pub fn get_markers(&self) -> Vec<MarkerInfo> {
456 self.tracks.get_markers()
457 }
458 pub fn get_marker(&self, marker_id: &str) -> Option<MarkerInfo> {
460 self.tracks.get_marker(marker_id)
461 }
462 pub fn add_marker(&mut self, owner_id: Option<&str>, marker: Marker) -> Option<String> {
464 self.tracks.add_marker(owner_id, marker)
465 }
466 pub fn update_marker(&mut self, marker_id: &str, update: MarkerUpdate) -> bool {
468 self.tracks.update_marker(marker_id, update)
469 }
470 pub fn delete_marker(&mut self, marker_id: &str) -> Option<Marker> {
472 self.tracks.delete_marker(marker_id)
473 }
474}