tellers-timeline-core 0.1.0

An OpenTimelineIO timeline engine with policy-driven editing, sync, sanitization, and effect conventions.
Documentation
use std::collections::HashMap;
use std::collections::HashSet;
use tellers_timeline_core::{
    Clip, IdMetadataExt, Item, MediaReference, RationalTime, Seconds, Stack, TimeRange, Track,
};

fn make_clip(duration: Seconds, media_start: Seconds) -> Item {
    let sr = TimeRange {
        otio_schema: "TimeRange.1".to_string(),
        duration: RationalTime {
            otio_schema: "RationalTime.1".to_string(),
            rate: 1.0,
            value: duration,
        },
        start_time: RationalTime {
            otio_schema: "RationalTime.1".to_string(),
            rate: 1.0,
            value: media_start,
        },
    };
    let mut refs: HashMap<String, MediaReference> = HashMap::new();
    refs.insert(
        "DEFAULT_MEDIA".to_string(),
        MediaReference::ExternalReference {
            target_url: "mem://".to_string(),
            available_range: None,
            name: None,
            available_image_bounds: None,
            metadata: serde_json::Value::Null,
        },
    );
    Item::Clip(Clip {
        otio_schema: "Clip.2".to_string(),
        enabled: true,
        name: None,
        source_range: sr,
        media_references: refs,
        active_media_reference_key: Some("DEFAULT_MEDIA".to_string()),
        metadata: serde_json::Value::Null,
        effects: Vec::new(),
        markers: Vec::new(),
    })
}

#[test]
fn split_clip_basic() {
    let mut track = Track::default();
    let mut clip = make_clip(10.0, 0.0);
    clip.set_id(Some("clip".to_string()));
    track.items.push(clip);
    let mut stack = Stack {
        children: vec![track],
        ..Stack::default()
    };

    assert!(stack.split_item_at_time("clip", 3.0));

    let track = &stack.children[0];
    assert_eq!(track.items.len(), 2);
    match (&track.items[0], &track.items[1]) {
        (Item::Clip(c0), Item::Clip(c1)) => {
            assert_eq!(c0.source_range.duration.value, 3.0);
            assert_eq!(c1.source_range.duration.value, 7.0);
            assert_eq!(c1.source_range.start_time.value, 3.0);
        }
        _ => panic!("expected two clips after split"),
    }
}

#[test]
fn split_at_boundary_noop() {
    let mut track = Track::default();
    let mut clip = make_clip(5.0, 0.0);
    clip.set_id(Some("clip".to_string()));
    track.items.push(clip);
    let mut stack = Stack {
        children: vec![track],
        ..Stack::default()
    };

    assert!(stack.split_item_at_time("clip", 0.0));
    assert_eq!(stack.children[0].items.len(), 1);

    assert!(stack.split_item_at_time("clip", 5.0));
    assert_eq!(stack.children[0].items.len(), 1);
}

#[test]
fn split_item_at_time_sanitizes_stack() {
    let mut first = Track::default();
    first.set_id(Some("duplicate".to_string()));
    let mut clip = make_clip(10.0, 0.0);
    clip.set_id(Some("clip".to_string()));
    first.items.push(clip);

    let mut second = Track::default();
    second.set_id(Some("duplicate".to_string()));
    let mut other = make_clip(1.0, 0.0);
    other.set_id(Some("clip".to_string()));
    second.items.push(other);

    let mut stack = Stack {
        children: vec![first, second],
        ..Stack::default()
    };

    assert!(stack.split_item_at_time("clip", 3.0));

    let ids: Vec<_> = stack
        .children
        .iter()
        .flat_map(|track| {
            std::iter::once(track.get_id().expect("track id")).chain(
                track
                    .items
                    .iter()
                    .map(|item| item.get_id().expect("item id")),
            )
        })
        .collect();
    let unique: HashSet<_> = ids.iter().collect();
    assert_eq!(ids.len(), unique.len());
}