1use std::collections::{HashMap, HashSet};
7
8use crate::clip::ClipId;
9use crate::error::{EditError, EditResult};
10
11pub type GroupId = u64;
13
14#[derive(Clone, Debug)]
16pub struct ClipGroup {
17 pub id: GroupId,
19 pub clips: HashSet<ClipId>,
21 pub name: Option<String>,
23 pub color: Option<[u8; 3]>,
25 pub locked: bool,
27}
28
29impl ClipGroup {
30 #[must_use]
32 pub fn new(id: GroupId) -> Self {
33 Self {
34 id,
35 clips: HashSet::new(),
36 name: None,
37 color: None,
38 locked: false,
39 }
40 }
41
42 pub fn add_clip(&mut self, clip_id: ClipId) {
44 self.clips.insert(clip_id);
45 }
46
47 pub fn remove_clip(&mut self, clip_id: ClipId) -> bool {
49 self.clips.remove(&clip_id)
50 }
51
52 #[must_use]
54 pub fn contains(&self, clip_id: ClipId) -> bool {
55 self.clips.contains(&clip_id)
56 }
57
58 #[must_use]
60 pub fn is_empty(&self) -> bool {
61 self.clips.is_empty()
62 }
63
64 #[must_use]
66 pub fn len(&self) -> usize {
67 self.clips.len()
68 }
69
70 #[must_use]
72 pub fn clip_ids(&self) -> Vec<ClipId> {
73 self.clips.iter().copied().collect()
74 }
75}
76
77#[derive(Debug, Default)]
79pub struct GroupManager {
80 groups: HashMap<GroupId, ClipGroup>,
82 clip_to_group: HashMap<ClipId, GroupId>,
84 next_id: GroupId,
86}
87
88impl GroupManager {
89 #[must_use]
91 pub fn new() -> Self {
92 Self {
93 groups: HashMap::new(),
94 clip_to_group: HashMap::new(),
95 next_id: 1,
96 }
97 }
98
99 pub fn create_group(&mut self) -> GroupId {
101 let id = self.next_id;
102 self.next_id += 1;
103 self.groups.insert(id, ClipGroup::new(id));
104 id
105 }
106
107 pub fn create_group_with_clips(&mut self, clips: Vec<ClipId>) -> EditResult<GroupId> {
109 let id = self.create_group();
110 for clip_id in clips {
111 self.add_to_group(id, clip_id)?;
112 }
113 Ok(id)
114 }
115
116 pub fn delete_group(&mut self, group_id: GroupId) -> Option<ClipGroup> {
118 if let Some(group) = self.groups.remove(&group_id) {
119 for clip_id in &group.clips {
121 self.clip_to_group.remove(clip_id);
122 }
123 Some(group)
124 } else {
125 None
126 }
127 }
128
129 pub fn add_to_group(&mut self, group_id: GroupId, clip_id: ClipId) -> EditResult<()> {
131 if self.clip_to_group.contains_key(&clip_id) {
133 return Err(EditError::InvalidEdit(
134 "Clip already in a group".to_string(),
135 ));
136 }
137
138 let group = self
139 .groups
140 .get_mut(&group_id)
141 .ok_or_else(|| EditError::InvalidEdit("Group not found".to_string()))?;
142
143 group.add_clip(clip_id);
144 self.clip_to_group.insert(clip_id, group_id);
145
146 Ok(())
147 }
148
149 pub fn remove_from_group(&mut self, clip_id: ClipId) -> Option<GroupId> {
151 if let Some(&group_id) = self.clip_to_group.get(&clip_id) {
152 if let Some(group) = self.groups.get_mut(&group_id) {
153 group.remove_clip(clip_id);
154 self.clip_to_group.remove(&clip_id);
155 return Some(group_id);
156 }
157 }
158 None
159 }
160
161 #[must_use]
163 pub fn get_clip_group(&self, clip_id: ClipId) -> Option<&ClipGroup> {
164 self.clip_to_group
165 .get(&clip_id)
166 .and_then(|&group_id| self.groups.get(&group_id))
167 }
168
169 #[must_use]
171 pub fn get_group(&self, group_id: GroupId) -> Option<&ClipGroup> {
172 self.groups.get(&group_id)
173 }
174
175 pub fn get_group_mut(&mut self, group_id: GroupId) -> Option<&mut ClipGroup> {
177 self.groups.get_mut(&group_id)
178 }
179
180 #[must_use]
182 pub fn all_groups(&self) -> Vec<&ClipGroup> {
183 self.groups.values().collect()
184 }
185
186 #[must_use]
188 pub fn is_grouped(&self, clip_id: ClipId) -> bool {
189 self.clip_to_group.contains_key(&clip_id)
190 }
191
192 #[must_use]
194 pub fn get_group_members(&self, clip_id: ClipId) -> Vec<ClipId> {
195 self.get_clip_group(clip_id)
196 .map(ClipGroup::clip_ids)
197 .unwrap_or_default()
198 }
199
200 pub fn clear(&mut self) {
202 self.groups.clear();
203 self.clip_to_group.clear();
204 }
205
206 #[must_use]
208 pub fn len(&self) -> usize {
209 self.groups.len()
210 }
211
212 #[must_use]
214 pub fn is_empty(&self) -> bool {
215 self.groups.is_empty()
216 }
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
221pub enum LinkType {
222 VideoAudio,
224 Synchronized,
226 ParentChild,
228 Custom,
230}
231
232#[derive(Clone, Debug)]
234pub struct ClipLink {
235 pub clip_a: ClipId,
237 pub clip_b: ClipId,
239 pub link_type: LinkType,
241 pub active: bool,
243}
244
245impl ClipLink {
246 #[must_use]
248 pub fn new(clip_a: ClipId, clip_b: ClipId, link_type: LinkType) -> Self {
249 Self {
250 clip_a,
251 clip_b,
252 link_type,
253 active: true,
254 }
255 }
256
257 #[must_use]
259 pub fn involves(&self, clip_id: ClipId) -> bool {
260 self.clip_a == clip_id || self.clip_b == clip_id
261 }
262
263 #[must_use]
265 pub fn other_clip(&self, clip_id: ClipId) -> Option<ClipId> {
266 if self.clip_a == clip_id {
267 Some(self.clip_b)
268 } else if self.clip_b == clip_id {
269 Some(self.clip_a)
270 } else {
271 None
272 }
273 }
274}
275
276#[derive(Debug, Default)]
278pub struct LinkManager {
279 links: Vec<ClipLink>,
281 clip_links: HashMap<ClipId, Vec<usize>>,
283}
284
285impl LinkManager {
286 #[must_use]
288 pub fn new() -> Self {
289 Self {
290 links: Vec::new(),
291 clip_links: HashMap::new(),
292 }
293 }
294
295 pub fn add_link(&mut self, clip_a: ClipId, clip_b: ClipId, link_type: LinkType) -> usize {
297 let index = self.links.len();
298 let link = ClipLink::new(clip_a, clip_b, link_type);
299
300 self.links.push(link);
301 self.clip_links.entry(clip_a).or_default().push(index);
302 self.clip_links.entry(clip_b).or_default().push(index);
303
304 index
305 }
306
307 pub fn link_video_audio(&mut self, video_clip: ClipId, audio_clip: ClipId) -> usize {
309 self.add_link(video_clip, audio_clip, LinkType::VideoAudio)
310 }
311
312 pub fn remove_link(&mut self, index: usize) -> Option<ClipLink> {
314 if index >= self.links.len() {
315 return None;
316 }
317
318 let link = self.links.remove(index);
319
320 self.clip_links.values_mut().for_each(|links| {
322 links.retain(|&i| i != index);
323 for link_idx in links.iter_mut() {
325 if *link_idx > index {
326 *link_idx -= 1;
327 }
328 }
329 });
330
331 Some(link)
332 }
333
334 pub fn remove_clip_links(&mut self, clip_id: ClipId) -> Vec<ClipLink> {
336 let link_indices: Vec<usize> = self.clip_links.get(&clip_id).cloned().unwrap_or_default();
337
338 let mut removed = Vec::new();
339 for &index in link_indices.iter().rev() {
341 if let Some(link) = self.remove_link(index) {
342 removed.push(link);
343 }
344 }
345
346 self.clip_links.remove(&clip_id);
347 removed
348 }
349
350 #[must_use]
352 pub fn get_clip_links(&self, clip_id: ClipId) -> Vec<&ClipLink> {
353 self.clip_links
354 .get(&clip_id)
355 .map(|indices| indices.iter().filter_map(|&i| self.links.get(i)).collect())
356 .unwrap_or_default()
357 }
358
359 #[must_use]
361 pub fn get_linked_clips(&self, clip_id: ClipId, link_type: LinkType) -> Vec<ClipId> {
362 self.get_clip_links(clip_id)
363 .into_iter()
364 .filter(|link| link.link_type == link_type && link.active)
365 .filter_map(|link| link.other_clip(clip_id))
366 .collect()
367 }
368
369 #[must_use]
371 pub fn are_linked(&self, clip_a: ClipId, clip_b: ClipId) -> bool {
372 self.get_clip_links(clip_a)
373 .iter()
374 .any(|link| link.involves(clip_b))
375 }
376
377 #[must_use]
379 pub fn active_links(&self) -> Vec<&ClipLink> {
380 self.links.iter().filter(|link| link.active).collect()
381 }
382
383 pub fn clear(&mut self) {
385 self.links.clear();
386 self.clip_links.clear();
387 }
388
389 #[must_use]
391 pub fn len(&self) -> usize {
392 self.links.len()
393 }
394
395 #[must_use]
397 pub fn is_empty(&self) -> bool {
398 self.links.is_empty()
399 }
400}
401
402#[derive(Clone, Debug)]
404pub struct CompoundClip {
405 pub id: ClipId,
407 pub clips: Vec<ClipId>,
409 pub name: String,
411 pub duration: i64,
413}
414
415impl CompoundClip {
416 #[must_use]
418 pub fn new(id: ClipId, name: String) -> Self {
419 Self {
420 id,
421 clips: Vec::new(),
422 name,
423 duration: 0,
424 }
425 }
426
427 pub fn add_clip(&mut self, clip_id: ClipId) {
429 self.clips.push(clip_id);
430 }
431
432 pub fn remove_clip(&mut self, clip_id: ClipId) -> bool {
434 if let Some(pos) = self.clips.iter().position(|&id| id == clip_id) {
435 self.clips.remove(pos);
436 true
437 } else {
438 false
439 }
440 }
441
442 #[must_use]
444 pub fn contains(&self, clip_id: ClipId) -> bool {
445 self.clips.contains(&clip_id)
446 }
447
448 #[must_use]
450 pub fn is_empty(&self) -> bool {
451 self.clips.is_empty()
452 }
453
454 #[must_use]
456 pub fn len(&self) -> usize {
457 self.clips.len()
458 }
459}
460
461#[derive(Debug, Default)]
463pub struct CompoundClipManager {
464 compounds: HashMap<ClipId, CompoundClip>,
466 clip_to_compound: HashMap<ClipId, ClipId>,
468}
469
470impl CompoundClipManager {
471 #[must_use]
473 pub fn new() -> Self {
474 Self {
475 compounds: HashMap::new(),
476 clip_to_compound: HashMap::new(),
477 }
478 }
479
480 pub fn create(&mut self, id: ClipId, name: String) -> ClipId {
482 let compound = CompoundClip::new(id, name);
483 self.compounds.insert(id, compound);
484 id
485 }
486
487 pub fn delete(&mut self, id: ClipId) -> Option<CompoundClip> {
489 if let Some(compound) = self.compounds.remove(&id) {
490 for &clip_id in &compound.clips {
492 self.clip_to_compound.remove(&clip_id);
493 }
494 Some(compound)
495 } else {
496 None
497 }
498 }
499
500 pub fn add_to_compound(&mut self, compound_id: ClipId, clip_id: ClipId) -> EditResult<()> {
502 if self.clip_to_compound.contains_key(&clip_id) {
504 return Err(EditError::InvalidEdit(
505 "Clip already in a compound".to_string(),
506 ));
507 }
508
509 let compound = self
510 .compounds
511 .get_mut(&compound_id)
512 .ok_or_else(|| EditError::InvalidEdit("Compound clip not found".to_string()))?;
513
514 compound.add_clip(clip_id);
515 self.clip_to_compound.insert(clip_id, compound_id);
516
517 Ok(())
518 }
519
520 pub fn remove_from_compound(&mut self, clip_id: ClipId) -> Option<ClipId> {
522 if let Some(&compound_id) = self.clip_to_compound.get(&clip_id) {
523 if let Some(compound) = self.compounds.get_mut(&compound_id) {
524 compound.remove_clip(clip_id);
525 self.clip_to_compound.remove(&clip_id);
526 return Some(compound_id);
527 }
528 }
529 None
530 }
531
532 #[must_use]
534 pub fn get_compound_for_clip(&self, clip_id: ClipId) -> Option<&CompoundClip> {
535 self.clip_to_compound
536 .get(&clip_id)
537 .and_then(|&compound_id| self.compounds.get(&compound_id))
538 }
539
540 #[must_use]
542 pub fn get(&self, id: ClipId) -> Option<&CompoundClip> {
543 self.compounds.get(&id)
544 }
545
546 pub fn get_mut(&mut self, id: ClipId) -> Option<&mut CompoundClip> {
548 self.compounds.get_mut(&id)
549 }
550
551 #[must_use]
553 pub fn all(&self) -> Vec<&CompoundClip> {
554 self.compounds.values().collect()
555 }
556
557 pub fn clear(&mut self) {
559 self.compounds.clear();
560 self.clip_to_compound.clear();
561 }
562
563 #[must_use]
565 pub fn len(&self) -> usize {
566 self.compounds.len()
567 }
568
569 #[must_use]
571 pub fn is_empty(&self) -> bool {
572 self.compounds.is_empty()
573 }
574}