1use crate::dictionary::Auid;
11use crate::object_model::{
12 Component, FillerSegment, Mob, MobSlot, MobType, OperationGroupSegment, Segment,
13 SequenceSegment, SourceClipSegment, TransitionSegment,
14};
15use crate::timeline::{EditRate, Position};
16use std::collections::HashMap;
17use uuid::Uuid;
18
19#[derive(Debug, Clone)]
21pub struct CompositionMob {
22 mob: Mob,
24 pub default_fade_length: Option<i64>,
26 pub default_fade_type: Option<FadeType>,
28 pub usage_code: Option<UsageCode>,
30}
31
32impl CompositionMob {
33 pub fn new(mob_id: Uuid, name: impl Into<String>) -> Self {
35 Self {
36 mob: Mob::new(mob_id, name.into(), MobType::Composition),
37 default_fade_length: None,
38 default_fade_type: None,
39 usage_code: None,
40 }
41 }
42
43 #[must_use]
45 pub fn mob_id(&self) -> Uuid {
46 self.mob.mob_id()
47 }
48
49 #[must_use]
51 pub fn name(&self) -> &str {
52 self.mob.name()
53 }
54
55 #[must_use]
57 pub fn tracks(&self) -> Vec<Track> {
58 self.mob
59 .slots()
60 .iter()
61 .map(|slot| Track::from_mob_slot(slot.clone()))
62 .collect()
63 }
64
65 #[must_use]
67 pub fn get_track(&self, slot_id: u32) -> Option<Track> {
68 self.mob
69 .get_slot(slot_id)
70 .map(|slot| Track::from_mob_slot(slot.clone()))
71 }
72
73 pub fn add_track(&mut self, track: Track) {
75 self.mob.add_slot(track.into_mob_slot());
76 }
77
78 #[must_use]
80 pub fn edit_rate(&self) -> Option<EditRate> {
81 self.tracks().first().map(|t| t.edit_rate)
82 }
83
84 #[must_use]
86 pub fn duration(&self) -> Option<i64> {
87 self.tracks().iter().filter_map(Track::duration).max()
88 }
89
90 #[must_use]
92 pub fn picture_tracks(&self) -> Vec<Track> {
93 self.tracks()
94 .into_iter()
95 .filter(Track::is_picture)
96 .collect()
97 }
98
99 #[must_use]
101 pub fn sound_tracks(&self) -> Vec<Track> {
102 self.tracks().into_iter().filter(Track::is_sound).collect()
103 }
104
105 #[must_use]
107 pub fn timecode_tracks(&self) -> Vec<Track> {
108 self.tracks()
109 .into_iter()
110 .filter(Track::is_timecode)
111 .collect()
112 }
113
114 #[must_use]
116 pub fn mob(&self) -> &Mob {
117 &self.mob
118 }
119
120 pub fn mob_mut(&mut self) -> &mut Mob {
122 &mut self.mob
123 }
124
125 pub fn set_default_fade(&mut self, length: i64, fade_type: FadeType) {
127 self.default_fade_length = Some(length);
128 self.default_fade_type = Some(fade_type);
129 }
130
131 pub fn set_usage_code(&mut self, code: UsageCode) {
133 self.usage_code = Some(code);
134 }
135
136 pub fn tracks_mut(&mut self) -> Vec<&mut MobSlot> {
142 self.mob.slots.iter_mut().collect()
143 }
144
145 pub fn insert_track_at(&mut self, index: usize, track: Track) {
150 let slot = track.into_mob_slot();
151 let len = self.mob.slots.len();
152 let idx = index.min(len);
153 self.mob.slots.insert(idx, slot);
154 }
155
156 pub fn remove_track_at(&mut self, index: usize) -> Option<Track> {
160 if index >= self.mob.slots.len() {
161 None
162 } else {
163 Some(Track::from_mob_slot(self.mob.slots.remove(index)))
164 }
165 }
166
167 pub fn move_track(&mut self, from_index: usize, to_index: usize) -> bool {
173 let len = self.mob.slots.len();
174 if from_index >= len || to_index >= len || from_index == to_index {
175 return false;
176 }
177 let slot = self.mob.slots.remove(from_index);
178 let adjusted_to = if to_index > from_index {
180 to_index - 1
181 } else {
182 to_index
183 };
184 self.mob.slots.insert(adjusted_to, slot);
185 true
186 }
187
188 pub fn reorder_tracks(&mut self, order: &[usize]) -> crate::Result<()> {
197 let len = self.mob.slots.len();
198 if order.len() != len {
199 return Err(crate::AafError::TimelineError(format!(
200 "reorder_tracks: order length {} != track count {}",
201 order.len(),
202 len
203 )));
204 }
205 let mut seen = vec![false; len];
207 for &idx in order {
208 if idx >= len {
209 return Err(crate::AafError::TimelineError(format!(
210 "reorder_tracks: index {idx} out of bounds (len={len})"
211 )));
212 }
213 if seen[idx] {
214 return Err(crate::AafError::TimelineError(format!(
215 "reorder_tracks: duplicate index {idx}"
216 )));
217 }
218 seen[idx] = true;
219 }
220 let old_slots = self.mob.slots.clone();
221 for (pos, &src_idx) in order.iter().enumerate() {
222 self.mob.slots[pos] = old_slots[src_idx].clone();
223 }
224 Ok(())
225 }
226
227 #[must_use]
229 pub fn track_count(&self) -> usize {
230 self.mob.slots.len()
231 }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum FadeType {
237 Linear,
239 Logarithmic,
241 Exponential,
243 SCurve,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum UsageCode {
250 TopLevel,
252 LowerLevel,
254 SubClip,
256 AdjustedClip,
258 Template,
260}
261
262#[derive(Debug, Clone)]
264pub struct Track {
265 pub track_id: u32,
267 pub name: String,
269 pub edit_rate: EditRate,
271 pub origin: Position,
273 pub physical_track_number: Option<u32>,
275 pub sequence: Option<Sequence>,
277 pub track_type: TrackType,
279}
280
281impl Track {
282 pub fn new(
284 track_id: u32,
285 name: impl Into<String>,
286 edit_rate: EditRate,
287 track_type: TrackType,
288 ) -> Self {
289 Self {
290 track_id,
291 name: name.into(),
292 edit_rate,
293 origin: Position::zero(),
294 physical_track_number: None,
295 sequence: None,
296 track_type,
297 }
298 }
299
300 #[must_use]
302 pub fn from_mob_slot(slot: MobSlot) -> Self {
303 let track_type = if let Some(ref segment) = slot.segment {
304 determine_track_type(segment.as_ref())
305 } else {
306 TrackType::Unknown
307 };
308
309 let sequence = if let Some(ref segment) = slot.segment {
310 extract_sequence(segment.as_ref())
311 } else {
312 None
313 };
314
315 Self {
316 track_id: slot.slot_id,
317 name: slot.name,
318 edit_rate: slot.edit_rate,
319 origin: slot.origin,
320 physical_track_number: slot.physical_track_number,
321 sequence,
322 track_type,
323 }
324 }
325
326 #[must_use]
328 pub fn into_mob_slot(self) -> MobSlot {
329 let segment = self
330 .sequence
331 .map(|sequence| Box::new(Segment::Sequence(sequence.into_segment())));
332
333 MobSlot {
334 slot_id: self.track_id,
335 name: self.name,
336 physical_track_number: self.physical_track_number,
337 edit_rate: self.edit_rate,
338 origin: self.origin,
339 segment,
340 slot_type: crate::object_model::SlotType::Timeline,
341 }
342 }
343
344 #[must_use]
346 pub fn duration(&self) -> Option<i64> {
347 self.sequence.as_ref().and_then(Sequence::duration)
348 }
349
350 #[must_use]
352 pub fn is_picture(&self) -> bool {
353 matches!(self.track_type, TrackType::Picture)
354 }
355
356 #[must_use]
358 pub fn is_sound(&self) -> bool {
359 matches!(self.track_type, TrackType::Sound)
360 }
361
362 #[must_use]
364 pub fn is_timecode(&self) -> bool {
365 matches!(self.track_type, TrackType::Timecode)
366 }
367
368 pub fn set_sequence(&mut self, sequence: Sequence) {
370 self.sequence = Some(sequence);
371 }
372
373 #[must_use]
375 pub fn source_clips(&self) -> Vec<&SourceClip> {
376 if let Some(ref sequence) = self.sequence {
377 sequence.source_clips()
378 } else {
379 Vec::new()
380 }
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum TrackType {
387 Picture,
389 Sound,
391 Timecode,
393 Data,
395 Unknown,
397}
398
399#[derive(Debug, Clone)]
401pub struct Sequence {
402 pub components: Vec<SequenceComponent>,
404 pub data_definition: Auid,
406}
407
408impl Sequence {
409 #[must_use]
411 pub fn new(data_definition: Auid) -> Self {
412 Self {
413 components: Vec::new(),
414 data_definition,
415 }
416 }
417
418 pub fn add_component(&mut self, component: SequenceComponent) {
420 self.components.push(component);
421 }
422
423 #[must_use]
425 pub fn duration(&self) -> Option<i64> {
426 let mut total = 0i64;
427 for component in &self.components {
428 total += component.length()?;
429 }
430 Some(total)
431 }
432
433 #[must_use]
435 pub fn source_clips(&self) -> Vec<&SourceClip> {
436 let mut clips = Vec::new();
437 for component in &self.components {
438 if let SequenceComponent::SourceClip(clip) = component {
439 clips.push(clip);
440 }
441 }
442 clips
443 }
444
445 #[must_use]
447 pub fn into_segment(self) -> SequenceSegment {
448 let components = self
449 .components
450 .into_iter()
451 .map(|c| c.into_component(self.data_definition))
452 .collect();
453
454 SequenceSegment {
455 components,
456 length: None,
457 }
458 }
459
460 #[must_use]
462 pub fn is_picture(&self) -> bool {
463 self.data_definition.is_picture()
464 }
465
466 #[must_use]
468 pub fn is_sound(&self) -> bool {
469 self.data_definition.is_sound()
470 }
471}
472
473#[derive(Debug, Clone)]
475pub enum SequenceComponent {
476 SourceClip(SourceClip),
478 Filler(Filler),
480 Transition(Transition),
482 Effect(Effect),
484}
485
486impl SequenceComponent {
487 #[must_use]
489 pub fn length(&self) -> Option<i64> {
490 match self {
491 SequenceComponent::SourceClip(clip) => Some(clip.length),
492 SequenceComponent::Filler(filler) => Some(filler.length),
493 SequenceComponent::Transition(transition) => Some(transition.length),
494 SequenceComponent::Effect(effect) => effect.length,
495 }
496 }
497
498 #[must_use]
500 pub fn into_component(self, data_definition: Auid) -> Component {
501 let segment = match self {
502 SequenceComponent::SourceClip(clip) => Segment::SourceClip(clip.into_segment()),
503 SequenceComponent::Filler(filler) => Segment::Filler(filler.into_segment()),
504 SequenceComponent::Transition(transition) => {
505 Segment::Transition(transition.into_segment())
506 }
507 SequenceComponent::Effect(effect) => Segment::OperationGroup(effect.into_segment()),
508 };
509
510 Component::new(data_definition, segment)
511 }
512}
513
514#[derive(Debug, Clone)]
516pub struct SourceClip {
517 pub length: i64,
519 pub start_time: Position,
521 pub source_mob_id: Uuid,
523 pub source_mob_slot_id: u32,
525 pub source_track_id: Option<u32>,
527}
528
529impl SourceClip {
530 #[must_use]
532 pub fn new(
533 length: i64,
534 start_time: Position,
535 source_mob_id: Uuid,
536 source_mob_slot_id: u32,
537 ) -> Self {
538 Self {
539 length,
540 start_time,
541 source_mob_id,
542 source_mob_slot_id,
543 source_track_id: None,
544 }
545 }
546
547 #[must_use]
549 pub fn with_source_track_id(mut self, track_id: u32) -> Self {
550 self.source_track_id = Some(track_id);
551 self
552 }
553
554 #[must_use]
556 pub fn into_segment(self) -> SourceClipSegment {
557 SourceClipSegment::new(
558 self.length,
559 self.start_time,
560 self.source_mob_id,
561 self.source_mob_slot_id,
562 )
563 }
564
565 #[must_use]
567 pub fn end_time(&self) -> Position {
568 Position(self.start_time.0 + self.length)
569 }
570}
571
572#[derive(Debug, Clone)]
574pub struct Filler {
575 pub length: i64,
577}
578
579impl Filler {
580 #[must_use]
582 pub fn new(length: i64) -> Self {
583 Self { length }
584 }
585
586 #[must_use]
588 pub fn into_segment(self) -> FillerSegment {
589 FillerSegment::new(self.length)
590 }
591}
592
593#[derive(Debug, Clone)]
595pub struct Transition {
596 pub length: i64,
598 pub cut_point: Position,
600 pub effect: Option<Effect>,
602}
603
604impl Transition {
605 #[must_use]
607 pub fn new(length: i64, cut_point: Position) -> Self {
608 Self {
609 length,
610 cut_point,
611 effect: None,
612 }
613 }
614
615 #[must_use]
617 pub fn with_effect(mut self, effect: Effect) -> Self {
618 self.effect = Some(effect);
619 self
620 }
621
622 #[must_use]
624 pub fn into_segment(self) -> TransitionSegment {
625 let effect = self.effect.map(|e| Box::new(e.into_segment()));
626
627 TransitionSegment {
628 length: self.length,
629 cut_point: self.cut_point,
630 effect,
631 }
632 }
633}
634
635#[derive(Debug, Clone)]
637pub struct Effect {
638 pub operation_id: Auid,
640 pub inputs: Vec<SequenceComponent>,
642 pub parameters: HashMap<String, EffectParameter>,
644 pub length: Option<i64>,
646}
647
648impl Effect {
649 #[must_use]
651 pub fn new(operation_id: Auid) -> Self {
652 Self {
653 operation_id,
654 inputs: Vec::new(),
655 parameters: HashMap::new(),
656 length: None,
657 }
658 }
659
660 pub fn add_input(&mut self, input: SequenceComponent) {
662 self.inputs.push(input);
663 }
664
665 pub fn add_parameter(&mut self, name: impl Into<String>, parameter: EffectParameter) {
667 self.parameters.insert(name.into(), parameter);
668 }
669
670 pub fn set_length(&mut self, length: i64) {
672 self.length = Some(length);
673 }
674
675 #[must_use]
677 pub fn into_segment(self) -> OperationGroupSegment {
678 OperationGroupSegment {
679 operation_id: self.operation_id,
680 input_segments: Vec::new(), parameters: Vec::new(), length: self.length,
683 }
684 }
685}
686
687#[derive(Debug, Clone)]
689pub enum EffectParameter {
690 Constant(f64),
692 Varying(Vec<Keyframe>),
694}
695
696#[derive(Debug, Clone)]
698pub struct Keyframe {
699 pub time: Position,
701 pub value: f64,
703 pub interpolation: InterpolationType,
705}
706
707impl Keyframe {
708 #[must_use]
710 pub fn new(time: Position, value: f64, interpolation: InterpolationType) -> Self {
711 Self {
712 time,
713 value,
714 interpolation,
715 }
716 }
717}
718
719#[derive(Debug, Clone, Copy, PartialEq, Eq)]
721pub enum InterpolationType {
722 None,
724 Linear,
726 Bezier,
728 Cubic,
730}
731
732fn determine_track_type(segment: &Segment) -> TrackType {
734 match segment {
735 Segment::Sequence(seq) => {
736 if let Some(component) = seq.components.first() {
737 if component.is_picture() {
738 TrackType::Picture
739 } else if component.is_sound() {
740 TrackType::Sound
741 } else if component.is_timecode() {
742 TrackType::Timecode
743 } else {
744 TrackType::Unknown
745 }
746 } else {
747 TrackType::Unknown
748 }
749 }
750 Segment::SourceClip(_) => TrackType::Unknown,
751 Segment::Filler(_) => TrackType::Unknown,
752 _ => TrackType::Unknown,
753 }
754}
755
756fn extract_sequence(segment: &Segment) -> Option<Sequence> {
758 match segment {
759 Segment::Sequence(seq) => {
760 let data_def = seq
761 .components
762 .first()
763 .map_or_else(Auid::null, |c| c.data_definition);
764
765 let mut sequence = Sequence::new(data_def);
766
767 for component in &seq.components {
768 if let Some(seq_component) = convert_component_to_sequence_component(component) {
769 sequence.add_component(seq_component);
770 }
771 }
772
773 Some(sequence)
774 }
775 _ => None,
776 }
777}
778
779fn convert_component_to_sequence_component(component: &Component) -> Option<SequenceComponent> {
781 match &component.segment {
782 Segment::SourceClip(clip) => Some(SequenceComponent::SourceClip(SourceClip {
783 length: clip.length,
784 start_time: clip.start_time,
785 source_mob_id: clip.source_mob_id,
786 source_mob_slot_id: clip.source_mob_slot_id,
787 source_track_id: None,
788 })),
789 Segment::Filler(filler) => Some(SequenceComponent::Filler(Filler {
790 length: filler.length,
791 })),
792 Segment::Transition(trans) => Some(SequenceComponent::Transition(Transition {
793 length: trans.length,
794 cut_point: trans.cut_point,
795 effect: None,
796 })),
797 _ => None,
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804
805 #[test]
806 fn test_composition_mob_creation() {
807 let mob_id = Uuid::new_v4();
808 let comp = CompositionMob::new(mob_id, "Test Composition");
809 assert_eq!(comp.mob_id(), mob_id);
810 assert_eq!(comp.name(), "Test Composition");
811 }
812
813 #[test]
814 fn test_track_creation() {
815 let track = Track::new(1, "Video", EditRate::PAL_25, TrackType::Picture);
816 assert_eq!(track.track_id, 1);
817 assert_eq!(track.name, "Video");
818 assert!(track.is_picture());
819 }
820
821 #[test]
822 fn test_sequence_creation() {
823 let seq = Sequence::new(Auid::PICTURE);
824 assert!(seq.is_picture());
825 assert_eq!(seq.duration(), Some(0));
826 }
827
828 #[test]
829 fn test_sequence_with_clips() {
830 let mut seq = Sequence::new(Auid::PICTURE);
831
832 let clip1 = SourceClip::new(100, Position::zero(), Uuid::new_v4(), 1);
833 let clip2 = SourceClip::new(50, Position::new(100), Uuid::new_v4(), 1);
834
835 seq.add_component(SequenceComponent::SourceClip(clip1));
836 seq.add_component(SequenceComponent::SourceClip(clip2));
837
838 assert_eq!(seq.duration(), Some(150));
839 assert_eq!(seq.source_clips().len(), 2);
840 }
841
842 #[test]
843 fn test_source_clip() {
844 let clip = SourceClip::new(100, Position::new(50), Uuid::new_v4(), 1);
845 assert_eq!(clip.length, 100);
846 assert_eq!(clip.start_time.0, 50);
847 assert_eq!(clip.end_time().0, 150);
848 }
849
850 #[test]
851 fn test_filler() {
852 let filler = Filler::new(25);
853 assert_eq!(filler.length, 25);
854 }
855
856 #[test]
857 fn test_transition() {
858 let trans = Transition::new(10, Position::new(5));
859 assert_eq!(trans.length, 10);
860 assert_eq!(trans.cut_point.0, 5);
861 }
862
863 #[test]
864 fn test_effect() {
865 let mut effect = Effect::new(Auid::null());
866 effect.set_length(50);
867 assert_eq!(effect.length, Some(50));
868 }
869
870 #[test]
871 fn test_keyframe() {
872 let kf = Keyframe::new(Position::new(10), 0.5, InterpolationType::Linear);
873 assert_eq!(kf.time.0, 10);
874 assert_eq!(kf.value, 0.5);
875 assert_eq!(kf.interpolation, InterpolationType::Linear);
876 }
877}