From 787f12bc211c849e4ba30bcdc8ea577a5dc2fa5d Mon Sep 17 00:00:00 2001 From: harshalsp07 <222931229+harshalsp07@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:20:52 +0530 Subject: [PATCH] [FIX] Honor 23.976 fps when importing SCC --- src/rust/src/demuxer/scc.rs | 27 +++++++++++++++++++------ src/rust/src/libccxr_exports/demuxer.rs | 2 +- tests/samples/scc_23_976.scc | 5 +++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 tests/samples/scc_23_976.scc diff --git a/src/rust/src/demuxer/scc.rs b/src/rust/src/demuxer/scc.rs index afdddf440..5ead2c524 100644 --- a/src/rust/src/demuxer/scc.rs +++ b/src/rust/src/demuxer/scc.rs @@ -15,7 +15,7 @@ //! - **Header:** `Scenarist_SCC V1.0` (first line) //! - **Lines:** `HH:MM:SS:FF\t` (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"; @@ -32,16 +32,19 @@ 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, } } @@ -49,10 +52,11 @@ impl SccFrameRate { /// 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 } } } @@ -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 diff --git a/src/rust/src/libccxr_exports/demuxer.rs b/src/rust/src/libccxr_exports/demuxer.rs index 18d02752b..837ab4571 100755 --- a/src/rust/src/libccxr_exports/demuxer.rs +++ b/src/rust/src/libccxr_exports/demuxer.rs @@ -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 /// diff --git a/tests/samples/scc_23_976.scc b/tests/samples/scc_23_976.scc new file mode 100644 index 000000000..533d04650 --- /dev/null +++ b/tests/samples/scc_23_976.scc @@ -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