Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/rust/src/demuxer/scc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! - **Header:** `Scenarist_SCC V1.0` (first line)
//! - **Lines:** `HH:MM:SS:FF\t<hex pairs>` (SMPTE timecode + TAB + space-separated hex bytes)
//! - **Hex pairs:** CEA-608 byte pairs with odd parity bits (e.g., `9420` = RCL command)
//! - **Frame rate:** 29.97fps NTSC drop-frame (default), also supports 24/25/30fps
//! - **Frame rate:** 29.97fps NTSC drop-frame (default), also supports 23.976/24/25/30fps

/// SCC format header
pub const SCC_HEADER: &[u8] = b"Scenarist_SCC V1.0";
Expand All @@ -32,27 +32,31 @@ pub enum SccFrameRate {
Fps25,
/// NTSC non-drop-frame - 30fps
Fps30,
/// NTSC film - 23.976fps
Fps23_976,
}

impl SccFrameRate {
/// Convert from integer value (used in FFI)
/// 0=29.97 (default), 1=24, 2=25, 3=30
/// 0=29.97 (default), 1=24, 2=25, 3=30, 4=23.976
pub fn from_int(value: i32) -> Self {
match value {
1 => SccFrameRate::Fps24,
2 => SccFrameRate::Fps25,
3 => SccFrameRate::Fps30,
4 => SccFrameRate::Fps23_976,
_ => SccFrameRate::Fps29_97,
}
}

/// Get milliseconds per frame for this frame rate
pub fn ms_per_frame(&self) -> f64 {
match self {
SccFrameRate::Fps29_97 => 1001.0 / 30.0, // ~33.37ms
SccFrameRate::Fps24 => 1000.0 / 24.0, // ~41.67ms
SccFrameRate::Fps25 => 1000.0 / 25.0, // 40ms
SccFrameRate::Fps30 => 1000.0 / 30.0, // ~33.33ms
SccFrameRate::Fps29_97 => 1001.0 / 30.0, // ~33.37ms
SccFrameRate::Fps24 => 1000.0 / 24.0, // ~41.67ms
SccFrameRate::Fps25 => 1000.0 / 25.0, // 40ms
SccFrameRate::Fps30 => 1000.0 / 30.0, // ~33.33ms
SccFrameRate::Fps23_976 => 1001.0 / 24.0, // ~41.71ms
}
}
}
Expand Down Expand Up @@ -261,9 +265,20 @@ mod tests {
assert_eq!(SccFrameRate::from_int(1), SccFrameRate::Fps24);
assert_eq!(SccFrameRate::from_int(2), SccFrameRate::Fps25);
assert_eq!(SccFrameRate::from_int(3), SccFrameRate::Fps30);
assert_eq!(SccFrameRate::from_int(4), SccFrameRate::Fps23_976);
assert_eq!(SccFrameRate::from_int(99), SccFrameRate::Fps29_97); // Invalid defaults
}

#[test]
fn test_parse_scc_uses_23_976_framerate_from_ffi() {
let content = "Scenarist_SCC V1.0\n\n00:00:01:12\t9420\n00:00:03:12\t942c\n";
let result = parse_scc(content, SccFrameRate::from_int(4));
assert_eq!(result.lines.len(), 2);
assert_eq!(result.lines[0].time_ms, 1500);
assert_eq!(result.lines[1].time_ms, 3500);
assert!((SccFrameRate::from_int(4).ms_per_frame() - 1001.0 / 24.0).abs() < 0.001);
}

#[test]
fn test_parse_smpte_timecode() {
// 29.97fps
Expand Down
2 changes: 1 addition & 1 deletion src/rust/src/libccxr_exports/demuxer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ pub unsafe extern "C" fn ccxr_is_scc_file(buffer: *const c_uchar, len: c_uint) -
///
/// # Arguments
///
/// - `framerate`: 0=29.97 (default), 1=24, 2=25, 3=30
/// - `framerate`: 0=29.97 (default), 1=24, 2=25, 3=30, 4=23.976
///
/// # Returns
///
Expand Down
5 changes: 5 additions & 0 deletions tests/samples/scc_23_976.scc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Scenarist_SCC V1.0

00:00:01:12 9420 9420 94e0 94e0 c849 942f 942f

00:00:03:12 942c 942c
Loading