Skip to main content

oximedia_aaf/
composition.rs

1//! Composition structures
2//!
3//! This module implements AAF composition objects:
4//! - `CompositionMob`: Represents an edit/sequence
5//! - Track: Individual timeline track
6//! - Sequence: Collection of segments
7//! - `SourceClip`: Reference to media
8//! - Composition hierarchy and navigation
9
10use 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/// Composition Mob - represents an edit/sequence
20#[derive(Debug, Clone)]
21pub struct CompositionMob {
22    /// Underlying mob
23    mob: Mob,
24    /// Default fade length
25    pub default_fade_length: Option<i64>,
26    /// Default fade type
27    pub default_fade_type: Option<FadeType>,
28    /// Usage code
29    pub usage_code: Option<UsageCode>,
30}
31
32impl CompositionMob {
33    /// Create a new composition mob
34    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    /// Get mob ID
44    #[must_use]
45    pub fn mob_id(&self) -> Uuid {
46        self.mob.mob_id()
47    }
48
49    /// Get name
50    #[must_use]
51    pub fn name(&self) -> &str {
52        self.mob.name()
53    }
54
55    /// Get tracks
56    #[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    /// Get track by slot ID
66    #[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    /// Add a track
74    pub fn add_track(&mut self, track: Track) {
75        self.mob.add_slot(track.into_mob_slot());
76    }
77
78    /// Get edit rate (from first track)
79    #[must_use]
80    pub fn edit_rate(&self) -> Option<EditRate> {
81        self.tracks().first().map(|t| t.edit_rate)
82    }
83
84    /// Get duration (from longest track)
85    #[must_use]
86    pub fn duration(&self) -> Option<i64> {
87        self.tracks().iter().filter_map(Track::duration).max()
88    }
89
90    /// Get all picture tracks
91    #[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    /// Get all sound tracks
100    #[must_use]
101    pub fn sound_tracks(&self) -> Vec<Track> {
102        self.tracks().into_iter().filter(Track::is_sound).collect()
103    }
104
105    /// Get all timecode tracks
106    #[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    /// Get the underlying mob
115    #[must_use]
116    pub fn mob(&self) -> &Mob {
117        &self.mob
118    }
119
120    /// Get mutable reference to underlying mob
121    pub fn mob_mut(&mut self) -> &mut Mob {
122        &mut self.mob
123    }
124
125    /// Set default fade
126    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    /// Set usage code
132    pub fn set_usage_code(&mut self, code: UsageCode) {
133        self.usage_code = Some(code);
134    }
135
136    // ─── Track re-ordering and insertion APIs ─────────────────────────────────
137
138    /// Return mutable `Track` objects — changes are applied to the underlying slots.
139    ///
140    /// Converts each slot to a `Track`, applies `f`, then writes back.
141    pub fn tracks_mut(&mut self) -> Vec<&mut MobSlot> {
142        self.mob.slots.iter_mut().collect()
143    }
144
145    /// Insert a new track at the given position (0-based index).
146    ///
147    /// All existing tracks at or after `index` are shifted right.
148    /// If `index` is greater than the current track count the track is appended.
149    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    /// Remove and return the track at the given slot index (position in the internal list).
157    ///
158    /// Returns `None` if `index` is out of bounds.
159    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    /// Move the track currently at `from_index` to `to_index`.
168    ///
169    /// Both indices are positions in the internal slot list.
170    /// If either index is out of bounds the operation is a no-op and
171    /// `false` is returned; otherwise `true`.
172    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        // After removal the target index may have shifted
179        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    /// Re-order all tracks according to a permutation vector.
189    ///
190    /// `order` must be a permutation of `0..track_count()`.  Each element
191    /// gives the *current* index of the track that should occupy that output
192    /// position.
193    ///
194    /// Returns `Ok(())` on success, or `AafError::TimelineError` if `order`
195    /// is not a valid permutation.
196    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        // Validate permutation
206        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    /// Get the number of tracks (slots)
228    #[must_use]
229    pub fn track_count(&self) -> usize {
230        self.mob.slots.len()
231    }
232}
233
234/// Fade type
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum FadeType {
237    /// Linear fade
238    Linear,
239    /// Logarithmic fade
240    Logarithmic,
241    /// Exponential fade
242    Exponential,
243    /// S-curve fade
244    SCurve,
245}
246
247/// Usage code for composition mobs
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum UsageCode {
250    /// Top level composition
251    TopLevel,
252    /// Lower level composition
253    LowerLevel,
254    /// Sub-clip
255    SubClip,
256    /// Adjusted clip
257    AdjustedClip,
258    /// Template
259    Template,
260}
261
262/// Track - represents a timeline track
263#[derive(Debug, Clone)]
264pub struct Track {
265    /// Track ID
266    pub track_id: u32,
267    /// Track name
268    pub name: String,
269    /// Edit rate
270    pub edit_rate: EditRate,
271    /// Origin (start position)
272    pub origin: Position,
273    /// Physical track number
274    pub physical_track_number: Option<u32>,
275    /// Sequence
276    pub sequence: Option<Sequence>,
277    /// Track type
278    pub track_type: TrackType,
279}
280
281impl Track {
282    /// Create a new track
283    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    /// Create from a mob slot
301    #[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    /// Convert to mob slot
327    #[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    /// Get duration
345    #[must_use]
346    pub fn duration(&self) -> Option<i64> {
347        self.sequence.as_ref().and_then(Sequence::duration)
348    }
349
350    /// Check if this is a picture track
351    #[must_use]
352    pub fn is_picture(&self) -> bool {
353        matches!(self.track_type, TrackType::Picture)
354    }
355
356    /// Check if this is a sound track
357    #[must_use]
358    pub fn is_sound(&self) -> bool {
359        matches!(self.track_type, TrackType::Sound)
360    }
361
362    /// Check if this is a timecode track
363    #[must_use]
364    pub fn is_timecode(&self) -> bool {
365        matches!(self.track_type, TrackType::Timecode)
366    }
367
368    /// Set sequence
369    pub fn set_sequence(&mut self, sequence: Sequence) {
370        self.sequence = Some(sequence);
371    }
372
373    /// Get all source clips in this track
374    #[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/// Track type
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum TrackType {
387    /// Picture/video track
388    Picture,
389    /// Sound/audio track
390    Sound,
391    /// Timecode track
392    Timecode,
393    /// Data track
394    Data,
395    /// Unknown track
396    Unknown,
397}
398
399/// Sequence - collection of segments in a track
400#[derive(Debug, Clone)]
401pub struct Sequence {
402    /// Components
403    pub components: Vec<SequenceComponent>,
404    /// Data definition
405    pub data_definition: Auid,
406}
407
408impl Sequence {
409    /// Create a new sequence
410    #[must_use]
411    pub fn new(data_definition: Auid) -> Self {
412        Self {
413            components: Vec::new(),
414            data_definition,
415        }
416    }
417
418    /// Add a component
419    pub fn add_component(&mut self, component: SequenceComponent) {
420        self.components.push(component);
421    }
422
423    /// Get duration
424    #[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    /// Get all source clips
434    #[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    /// Convert to segment
446    #[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    /// Check if this is a picture sequence
461    #[must_use]
462    pub fn is_picture(&self) -> bool {
463        self.data_definition.is_picture()
464    }
465
466    /// Check if this is a sound sequence
467    #[must_use]
468    pub fn is_sound(&self) -> bool {
469        self.data_definition.is_sound()
470    }
471}
472
473/// Sequence component types
474#[derive(Debug, Clone)]
475pub enum SequenceComponent {
476    /// Source clip
477    SourceClip(SourceClip),
478    /// Filler
479    Filler(Filler),
480    /// Transition
481    Transition(Transition),
482    /// Effect
483    Effect(Effect),
484}
485
486impl SequenceComponent {
487    /// Get length
488    #[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    /// Convert to component
499    #[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/// Source clip - reference to media
515#[derive(Debug, Clone)]
516pub struct SourceClip {
517    /// Length
518    pub length: i64,
519    /// Start time in source
520    pub start_time: Position,
521    /// Source mob ID
522    pub source_mob_id: Uuid,
523    /// Source mob slot ID
524    pub source_mob_slot_id: u32,
525    /// Source track ID
526    pub source_track_id: Option<u32>,
527}
528
529impl SourceClip {
530    /// Create a new source clip
531    #[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    /// Set source track ID
548    #[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    /// Convert to segment
555    #[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    /// Get end time in source
566    #[must_use]
567    pub fn end_time(&self) -> Position {
568        Position(self.start_time.0 + self.length)
569    }
570}
571
572/// Filler - gap in the timeline
573#[derive(Debug, Clone)]
574pub struct Filler {
575    /// Length
576    pub length: i64,
577}
578
579impl Filler {
580    /// Create a new filler
581    #[must_use]
582    pub fn new(length: i64) -> Self {
583        Self { length }
584    }
585
586    /// Convert to segment
587    #[must_use]
588    pub fn into_segment(self) -> FillerSegment {
589        FillerSegment::new(self.length)
590    }
591}
592
593/// Transition - dissolve, wipe, etc.
594#[derive(Debug, Clone)]
595pub struct Transition {
596    /// Length
597    pub length: i64,
598    /// Cut point
599    pub cut_point: Position,
600    /// Effect (optional)
601    pub effect: Option<Effect>,
602}
603
604impl Transition {
605    /// Create a new transition
606    #[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    /// Set effect
616    #[must_use]
617    pub fn with_effect(mut self, effect: Effect) -> Self {
618        self.effect = Some(effect);
619        self
620    }
621
622    /// Convert to segment
623    #[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/// Effect - operation applied to segments
636#[derive(Debug, Clone)]
637pub struct Effect {
638    /// Operation ID
639    pub operation_id: Auid,
640    /// Input segments
641    pub inputs: Vec<SequenceComponent>,
642    /// Parameters
643    pub parameters: HashMap<String, EffectParameter>,
644    /// Length
645    pub length: Option<i64>,
646}
647
648impl Effect {
649    /// Create a new effect
650    #[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    /// Add input
661    pub fn add_input(&mut self, input: SequenceComponent) {
662        self.inputs.push(input);
663    }
664
665    /// Add parameter
666    pub fn add_parameter(&mut self, name: impl Into<String>, parameter: EffectParameter) {
667        self.parameters.insert(name.into(), parameter);
668    }
669
670    /// Set length
671    pub fn set_length(&mut self, length: i64) {
672        self.length = Some(length);
673    }
674
675    /// Convert to segment
676    #[must_use]
677    pub fn into_segment(self) -> OperationGroupSegment {
678        OperationGroupSegment {
679            operation_id: self.operation_id,
680            input_segments: Vec::new(), // Would need conversion
681            parameters: Vec::new(),     // Would need conversion
682            length: self.length,
683        }
684    }
685}
686
687/// Effect parameter
688#[derive(Debug, Clone)]
689pub enum EffectParameter {
690    /// Constant value
691    Constant(f64),
692    /// Varying value (keyframes)
693    Varying(Vec<Keyframe>),
694}
695
696/// Keyframe for effect parameters
697#[derive(Debug, Clone)]
698pub struct Keyframe {
699    /// Time position
700    pub time: Position,
701    /// Value
702    pub value: f64,
703    /// Interpolation
704    pub interpolation: InterpolationType,
705}
706
707impl Keyframe {
708    /// Create a new keyframe
709    #[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/// Interpolation type
720#[derive(Debug, Clone, Copy, PartialEq, Eq)]
721pub enum InterpolationType {
722    /// No interpolation (step)
723    None,
724    /// Linear interpolation
725    Linear,
726    /// Bezier interpolation
727    Bezier,
728    /// Cubic interpolation
729    Cubic,
730}
731
732/// Helper to determine track type from segment
733fn 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
756/// Helper to extract sequence from segment
757fn 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
779/// Helper to convert Component to `SequenceComponent`
780fn 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}