vidsage-core 0.1.0

Core functionality for VidSage video processing and AI commentary generation
Documentation
//! Helper functions

use chrono::Duration;
use std::path::Path;
use uuid::Uuid;

/// Generate a unique ID
pub fn generate_id() -> String {
    Uuid::new_v4().to_string()
}

/// Format a duration in seconds to a human-readable string
pub fn format_duration(seconds: f64) -> String {
    let duration = Duration::seconds(seconds as i64);
    let hours = duration.num_hours();
    let minutes = duration.num_minutes() % 60;
    let secs = duration.num_seconds() % 60;
    let millis = duration.num_milliseconds() % 1000;

    if hours > 0 {
        format!("{}h {}m {}.{}s", hours, minutes, secs, millis)
    } else if minutes > 0 {
        format!("{}m {}.{}s", minutes, secs, millis)
    } else {
        format!("{}.{}s", secs, millis)
    }
}

/// Format a file size in bytes to a human-readable string
pub fn format_file_size(bytes: u64) -> String {
    let units = ["B", "KB", "MB", "GB", "TB"];
    let mut size = bytes as f64;
    let mut unit_index = 0;

    while size >= 1024.0 && unit_index < units.len() - 1 {
        size /= 1024.0;
        unit_index += 1;
    }

    format!("{:.2} {}", size, units[unit_index])
}

/// Get the file extension from a path
pub fn get_file_extension(path: &Path) -> Option<String> {
    path.extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_lowercase())
}

/// Check if a file has a video extension
pub fn is_video_file(path: &Path) -> bool {
    const VIDEO_EXTENSIONS: &[&str] = &["mp4", "webm", "avi", "mov", "mkv", "flv", "wmv", "m4v"];

    if let Some(ext) = get_file_extension(path) {
        VIDEO_EXTENSIONS.contains(&ext.as_str())
    } else {
        false
    }
}

/// Check if a file has an audio extension
pub fn is_audio_file(path: &Path) -> bool {
    const AUDIO_EXTENSIONS: &[&str] = &["mp3", "wav", "ogg", "flac", "aac", "wma", "m4a"];

    if let Some(ext) = get_file_extension(path) {
        AUDIO_EXTENSIONS.contains(&ext.as_str())
    } else {
        false
    }
}

/// Truncate a string to a maximum length with an ellipsis
pub fn truncate_string(s: &str, max_len: usize) -> String {
    if s.len() <= max_len {
        s.to_string()
    } else {
        format!("{}{}", &s[..max_len - 3], "...")
    }
}

/// Validate a UUID string
pub fn is_valid_uuid(uuid: &str) -> bool {
    Uuid::parse_str(uuid).is_ok()
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;

    #[test]
    fn test_generate_id() {
        let id1 = generate_id();
        let id2 = generate_id();
        assert_ne!(id1, id2);
        assert_eq!(id1.len(), 36); // UUID v4 length
    }

    #[test]
    fn test_format_duration() {
        assert_eq!(format_duration(60.0), "1m 0.0s");
        assert_eq!(format_duration(3600.0), "1h 0m 0.0s");
        assert_eq!(format_duration(3661.5), "1h 1m 1.0s");
        assert_eq!(format_duration(0.0), "0.0s");
    }

    #[test]
    fn test_format_file_size() {
        assert_eq!(format_file_size(1024), "1.00 KB");
        assert_eq!(format_file_size(1024 * 1024), "1.00 MB");
        assert_eq!(format_file_size(500), "500.00 B");
    }

    #[test]
    fn test_get_file_extension() {
        let path = Path::new("video.mp4");
        assert_eq!(get_file_extension(path), Some("mp4".to_string()));

        let path = Path::new("audio.MP3");
        assert_eq!(get_file_extension(path), Some("mp3".to_string()));

        let path = Path::new("file");
        assert_eq!(get_file_extension(path), None);
    }

    #[test]
    fn test_is_video_file() {
        let video_path = Path::new("video.mp4");
        let audio_path = Path::new("audio.mp3");
        let text_path = Path::new("text.txt");

        assert!(is_video_file(video_path));
        assert!(!is_video_file(audio_path));
        assert!(!is_video_file(text_path));
    }

    #[test]
    fn test_is_audio_file() {
        let audio_path = Path::new("audio.mp3");
        let video_path = Path::new("video.mp4");
        let text_path = Path::new("text.txt");

        assert!(is_audio_file(audio_path));
        assert!(!is_audio_file(video_path));
        assert!(!is_audio_file(text_path));
    }

    #[test]
    fn test_truncate_string() {
        let long_str = "This is a very long string that needs to be truncated";
        assert_eq!(truncate_string(long_str, 20), "This is a very lo...");
        assert_eq!(truncate_string(long_str, 100), long_str);
    }

    #[test]
    fn test_is_valid_uuid() {
        let valid_uuid = "123e4567-e89b-12d3-a456-426614174000";
        let invalid_uuid = "invalid-uuid";

        assert!(is_valid_uuid(valid_uuid));
        assert!(!is_valid_uuid(invalid_uuid));
    }
}