#![allow(dead_code)]
use crate::error::{EdlError, EdlResult};
use crate::event::EdlEvent;
use crate::parser::EdlParser;
use crate::timecode::EdlFrameRate;
use std::cell::RefCell;
#[derive(Debug, Clone)]
pub struct LazyEventHeader {
pub number: u32,
pub reel: String,
pub line_start: usize,
pub line_end: usize,
}
#[derive(Debug)]
pub struct LazyEdl {
source: String,
pub title: Option<String>,
pub frame_rate: EdlFrameRate,
headers: Vec<LazyEventHeader>,
cache: RefCell<Vec<Option<EdlEvent>>>,
}
impl LazyEdl {
pub fn from_str(input: &str) -> EdlResult<Self> {
let mut title: Option<String> = None;
let mut frame_rate = EdlFrameRate::Fps2997NDF;
let mut headers = Vec::new();
let mut current_header: Option<LazyEventHeader> = None;
let lines: Vec<&str> = input.lines().collect();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.starts_with("TITLE:") {
title = Some(trimmed.trim_start_matches("TITLE:").trim().to_string());
continue;
}
if trimmed.starts_with("FCM:") {
let fcm = trimmed.trim_start_matches("FCM:").trim().to_uppercase();
frame_rate = if fcm.contains("NON") {
EdlFrameRate::Fps2997NDF
} else if fcm.contains("DROP") {
EdlFrameRate::Fps2997DF
} else {
EdlFrameRate::Fps2997NDF
};
continue;
}
if trimmed.starts_with('*') {
if let Some(h) = &mut current_header {
h.line_end = idx + 1;
}
continue;
}
if trimmed.chars().next().map_or(false, |c| c.is_ascii_digit()) {
if let Some(h) = current_header.take() {
headers.push(h);
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 {
let number = parts[0].parse::<u32>().unwrap_or(0);
let reel = parts[1].to_string();
current_header = Some(LazyEventHeader {
number,
reel,
line_start: idx,
line_end: idx + 1,
});
}
}
}
if let Some(h) = current_header {
headers.push(h);
}
let cache_len = headers.len();
Ok(Self {
source: input.to_string(),
title,
frame_rate,
headers,
cache: RefCell::new(vec![None; cache_len]),
})
}
#[must_use]
pub fn event_count(&self) -> usize {
self.headers.len()
}
#[must_use]
pub fn headers(&self) -> &[LazyEventHeader] {
&self.headers
}
#[must_use]
pub fn header(&self, index: usize) -> Option<&LazyEventHeader> {
self.headers.get(index)
}
#[must_use]
pub fn event_numbers(&self) -> Vec<u32> {
self.headers.iter().map(|h| h.number).collect()
}
#[must_use]
pub fn reel_names(&self) -> Vec<&str> {
self.headers.iter().map(|h| h.reel.as_str()).collect()
}
pub fn get_event(&self, index: usize) -> EdlResult<EdlEvent> {
{
let cache = self.cache.borrow();
if let Some(Some(event)) = cache.get(index) {
return Ok(event.clone());
}
}
let header = self
.headers
.get(index)
.ok_or(EdlError::EventNotFound(index as u32))?;
let lines: Vec<&str> = self.source.lines().collect();
let event_text: String = lines
.get(header.line_start..header.line_end)
.unwrap_or(&[])
.join("\n");
let mut parser = EdlParser::new();
let mini_edl = parser.parse(&event_text)?;
let event = mini_edl
.events
.into_iter()
.next()
.ok_or_else(|| EdlError::parse(header.line_start, "Failed to parse event"))?;
{
let mut cache = self.cache.borrow_mut();
if let Some(slot) = cache.get_mut(index) {
*slot = Some(event.clone());
}
}
Ok(event)
}
#[must_use]
pub fn find_by_reel(&self, reel: &str) -> Vec<usize> {
self.headers
.iter()
.enumerate()
.filter(|(_, h)| h.reel == reel)
.map(|(i, _)| i)
.collect()
}
pub fn parse_all(&self) -> EdlResult<Vec<EdlEvent>> {
let mut events = Vec::with_capacity(self.headers.len());
for i in 0..self.headers.len() {
events.push(self.get_event(i)?);
}
Ok(events)
}
#[must_use]
pub fn cached_count(&self) -> usize {
self.cache.borrow().iter().filter(|e| e.is_some()).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_EDL: &str = "TITLE: Lazy Test\n\
FCM: NON-DROP FRAME\n\
\n\
001 A001 V C 01:00:00:00 01:00:05:00 01:00:00:00 01:00:05:00\n\
* FROM CLIP NAME: shot001.mov\n\
\n\
002 A002 V C 01:00:05:00 01:00:10:00 01:00:05:00 01:00:10:00\n\
* FROM CLIP NAME: shot002.mov\n\
\n\
003 B001 V C 01:00:10:00 01:00:15:00 01:00:10:00 01:00:15:00\n";
#[test]
fn test_lazy_parse_headers() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
assert_eq!(lazy.title, Some("Lazy Test".to_string()));
assert_eq!(lazy.frame_rate, EdlFrameRate::Fps2997NDF);
assert_eq!(lazy.event_count(), 3);
}
#[test]
fn test_lazy_event_numbers() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
assert_eq!(lazy.event_numbers(), vec![1, 2, 3]);
}
#[test]
fn test_lazy_reel_names() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
assert_eq!(lazy.reel_names(), vec!["A001", "A002", "B001"]);
}
#[test]
fn test_lazy_headers() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
let h = lazy.header(0).expect("header should exist");
assert_eq!(h.number, 1);
assert_eq!(h.reel, "A001");
}
#[test]
fn test_lazy_get_event() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
assert_eq!(lazy.cached_count(), 0);
let event = lazy.get_event(0).expect("event should parse");
assert_eq!(event.number, 1);
assert_eq!(event.reel, "A001");
assert_eq!(lazy.cached_count(), 1);
let event2 = lazy.get_event(0).expect("cached event should return");
assert_eq!(event2.number, 1);
assert_eq!(lazy.cached_count(), 1);
}
#[test]
fn test_lazy_get_event_with_clip_name() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
let event = lazy.get_event(0).expect("event should parse");
assert_eq!(event.clip_name, Some("shot001.mov".to_string()));
}
#[test]
fn test_lazy_get_all_events() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
let events = lazy.parse_all().expect("parse_all should succeed");
assert_eq!(events.len(), 3);
assert_eq!(events[2].reel, "B001");
assert_eq!(lazy.cached_count(), 3);
}
#[test]
fn test_lazy_find_by_reel() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
let indices = lazy.find_by_reel("A001");
assert_eq!(indices, vec![0]);
let indices2 = lazy.find_by_reel("B001");
assert_eq!(indices2, vec![2]);
let indices3 = lazy.find_by_reel("MISSING");
assert!(indices3.is_empty());
}
#[test]
fn test_lazy_event_out_of_bounds() {
let lazy = LazyEdl::from_str(SAMPLE_EDL).expect("parse should succeed");
assert!(lazy.get_event(99).is_err());
}
#[test]
fn test_lazy_empty_edl() {
let lazy =
LazyEdl::from_str("TITLE: Empty\nFCM: DROP FRAME\n").expect("parse should succeed");
assert_eq!(lazy.event_count(), 0);
assert_eq!(lazy.title, Some("Empty".to_string()));
}
#[test]
fn test_lazy_drop_frame() {
let edl =
"FCM: DROP FRAME\n001 AX V C 01:00:00;00 01:00:05;00 01:00:00;00 01:00:05;00\n";
let lazy = LazyEdl::from_str(edl).expect("parse should succeed");
assert_eq!(lazy.frame_rate, EdlFrameRate::Fps2997DF);
}
}