use super::ClipId;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SubClipId(Uuid);
impl SubClipId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub const fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
#[must_use]
pub const fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for SubClipId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for SubClipId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubClip {
pub id: SubClipId,
pub parent_clip_id: ClipId,
pub name: String,
pub in_point: i64,
pub out_point: i64,
pub description: Option<String>,
}
impl SubClip {
#[must_use]
pub fn new(
parent_clip_id: ClipId,
name: impl Into<String>,
in_point: i64,
out_point: i64,
) -> Self {
Self {
id: SubClipId::new(),
parent_clip_id,
name: name.into(),
in_point,
out_point,
description: None,
}
}
#[must_use]
pub const fn duration(&self) -> i64 {
self.out_point - self.in_point
}
#[must_use]
pub const fn is_valid(&self) -> bool {
self.in_point < self.out_point
}
pub fn set_description(&mut self, description: impl Into<String>) {
self.description = Some(description.into());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subclip_creation() {
let parent_id = ClipId::new();
let subclip = SubClip::new(parent_id, "Action Scene", 100, 500);
assert_eq!(subclip.name, "Action Scene");
assert_eq!(subclip.in_point, 100);
assert_eq!(subclip.out_point, 500);
assert_eq!(subclip.duration(), 400);
assert!(subclip.is_valid());
}
#[test]
fn test_invalid_subclip() {
let parent_id = ClipId::new();
let subclip = SubClip::new(parent_id, "Invalid", 500, 100);
assert!(!subclip.is_valid());
}
#[test]
fn test_subclip_boundary_conditions() {
let parent_id = ClipId::new();
let clip_duration: i64 = 2400;
let full = SubClip::new(parent_id, "Full Range", 0, clip_duration);
assert!(
full.is_valid(),
"start=0, end=clip_duration should be valid"
);
assert_eq!(full.in_point, 0, "in_point should be 0");
assert_eq!(
full.out_point, clip_duration,
"out_point should equal clip_duration"
);
assert_eq!(
full.duration(),
clip_duration,
"duration should equal clip_duration"
);
let first_frame = SubClip::new(parent_id, "First Frame", 0, 1);
assert!(
first_frame.is_valid(),
"single-frame subclip at start should be valid"
);
assert_eq!(first_frame.duration(), 1);
let last_frame = SubClip::new(parent_id, "Last Frame", clip_duration - 1, clip_duration);
assert!(
last_frame.is_valid(),
"single-frame subclip at end should be valid"
);
assert_eq!(last_frame.duration(), 1);
let zero_dur = SubClip::new(parent_id, "Zero Duration", 0, 0);
assert!(
!zero_dur.is_valid(),
"zero-duration subclip should not be valid"
);
assert_eq!(zero_dur.duration(), 0);
let end_zero = SubClip::new(parent_id, "End Zero", clip_duration, clip_duration);
assert!(
!end_zero.is_valid(),
"zero-duration subclip at end boundary should not be valid"
);
}
}