Skip to content

BUG: SCC timecodes use hardcoded 29.97 FPS instead of actual stream frame rate #2145

Open

Description

@Varadraj75

Summary

print_scc_time() in src/lib_ccx/ccx_common_timing.c computes SCC timecode frame numbers using a hardcoded literal 29.97, completely ignoring the current_fps global that exists in the same file and is dynamically updated from stream NAL data at runtime.

Any SCC output from a 24fps, 25fps, 30fps, or 50/60fps source will have incorrect frame numbers in every timecode, making the output non-standard and potentially rejected by downstream validators.


Affected File

src/lib_ccx/ccx_common_timing.c — Line 125


Reproduction

grep -n "29\.97" src/lib_ccx/ccx_common_timing.c
22: double current_fps = (double)30000.0 / 1001; /* 29.97 */ // TODO: Get from framerates_values[] instead
125: frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * 29.97 / 1000);

Root Cause

current_fps is already updated dynamically in two places:

  • avc_functions.c:891 — from stream NAL timing data
  • avc_functions.c:991 and es_functions.c:442 — from framerates_values[current_frame_rate]

print_scc_time() is simply the one function that was never updated to use it.


Impact

  • All non-NTSC sources (24fps film, 25fps PAL, 30fps progressive) produce SCC timecodes with wrong frame numbers
  • 25fps PAL broadcasts — common across Europe — see a ~20% frame count error
  • Downstream broadcast validators may reject the output or apply incorrect sync

Suggested Fix

Single-token change on line 125:

// BEFORE:
frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * 29.97 / 1000);

// AFTER:
frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * current_fps / 1000);

current_fps is a file-level global in the same translation unit — no header changes needed. Happy to submit a PR for this.

Activity

  1. bbgdzxng1 commented on Feb 26, 2026

    @bbgdzxng1

    This one is a bit of a tricky one.

    Technically, the only true SCC (EIA-608) framerate is 29.97, and even if you delivered a SCC file to be used to accompany a 23.98fps movie, you were still expected to deliver the SCC at 29.97... because back in the day, SCC had one job and one job only - authoring NTSC DVDs for Sonic Solutions SCENARIST DVD authoring software for Region 1 - and the Picture User Data headers for both soft and hard telecine end up as 29.97 on disc.

    Now, two decades later, a lot has changed and the use cases for SCC have expanded beyond the original DVD-Video remit, but there is still an industry practice that SCCs can use a 29.97 timecode even if the base media uses other frame rates, unless specifically overridden and communicated between parties. Telestream, who know a lot about pro-workflows for closed captioning claim...

    https://support.telestream.net/s/article/SCC-Files-and-23-98-fps-or-Other-Frame-Rates

    "For best compatibility, SCC files should almost always be exported in 29.97 time code mode. The main consideration is whether to choose 29.97 drop frame (DF) or 29.97 non-drop frame (NDF) time code. As long as the recipient knows which mode the SCC is, it should work fine regardless of whether the video itself is DF or NDF. Trouble can arise if you export the SCC in one mode, but the recipient assumes it is in the opposite mode. This trouble can manifest as timing issues, such as the captions drifting out of sync (slowly becoming earlier or later).

    When someone asks for a "23.98 fps SCC" file, most of the time, what they really want is a 29.97 NDF (non-drop frame) .scc file. Likewise, if they provide to you a so called "23.98 fps SCC file", it's probably a 29.97 NDF .scc file. Standard industry best practice is to combine a 29.97 NDF .scc file with a 23.98 fps video file. Captioning systems are equipped to internally handle this conversion properly."

    That doesn't mean that ccextractor should not support other framerates, but it does muddy the waters a little. Thus there are two camps "29.97-only-let-the-next-system-deal-with-it" and "same-as-media" camps... and that leaves a challenge for ccextractor's expected default behavior (including potential DF, NDF). I also appreciate that Telestream's FAQ may be one focused on pro-deliveries to DVD authoring systems, and is likely US-centric.

    Non-29.97 is a perfectly reasonable modern SCC use-case, especially now DVD-Video mastering is kinda solved, film is now transported progressive, non-NTSC markets do exist and for the support of video players that are unaware of the legacy convention.

    It is the age-old question of convention vs technical frame-level accuracy. The two downsides if you get it wrong are:

    • +/- 4 frame accuracy for the start frame of a caption cue
    • cue starting in the {25...29} range being treated as out-of-range

    Sophisticated authoring systems (and IIRC maybe even mediainfo run agains an SCC input?) can include heuristic checks, sometimes using maximum known frame number or even statistical frequency analysis to count the number of timecodes that fit only into one of {0..23}, {0..24} and {0..29} to make a reasonable guess as to the actual frame rate of an SCC input files. With enough video frames, if no frames land in the {25-29} range, you can guess it is likely to be film or PAL.

    I offer no opinion, other than quoting Telestream's statement on 29.97 and highlighting that there are complexities around choosing a sensible default behavior, while still offering flexibility for those requiring "same-as-media" workflows.

  2. Varadraj75 commented on Feb 26, 2026

    @Varadraj75
    ContributorAuthor

    Thank you for the detailed context — that Telestream reference is really helpful and I wasn't
    aware of the historical convention around 29.97 as the universal SCC delivery standard.

    That said, I think we agree on the core: the hardcoded literal is still a problem, the question
    is just what the default behavior should be and whether the user should have control over it.

    A reasonable middle ground might be:

    • Keep 29.97 as the default (honoring the industry convention you described)
    • Add a flag like --scc-fps or respect current_fps when explicitly set by the user
    • This way "29.97-only-let-the-next-system-deal-with-it" workflows are unchanged, while
      "same-as-media" users get an opt-in path

    Would that direction make sense to the maintainers? Happy to draft a PR along those lines
    if there's appetite for it.

  3. bbgdzxng1 commented on Feb 26, 2026

    @bbgdzxng1

    BTW - I have not tested #1916

    "SCC Output Frame Rate: The --scc-framerate option now affects both input parsing AND output encoding (was hardcoded to 29.97fps)"

    $ ccextractor --help | grep "scc-framerate"  
          --scc-framerate <fps>
              Set the frame rate for SCC (Scenarist Closed Caption) input files.
              Valid values: 29.97 (default), 24, 25, 30
              Example: --scc-framerate 25
    
  4. Varadraj75 commented on Feb 26, 2026

    @Varadraj75
    ContributorAuthor

    Thanks for digging that up! Looks like --scc-framerate from #1916 already covers the
    use case I was describing. I'll test it against a 25fps source to verify the output
    timecodes are correct end-to-end.

    If the flag works as documented, I'll close this issue. Apologies for the noise —
    I was looking at the source and missed that the CLI option existed.

  5. added a commit that references this issue on Feb 26, 2026
    e6a98fd
  6. Atul-Chahar commented on Feb 26, 2026

    @Atul-Chahar
    Contributor

    PR Submitted

    I've created a PR that addresses this issue: #2146

    Verification:

    • The issue is in print_scc_time() function in ccx_common_timing.c (line 125)
    • This is a different code path from the --scc-framerate option in add_timestamp() (ccx_encoders_scc.c)
    • The --scc-framerate CLI option controls output via get_scc_fps(context->scc_framerate)
    • This fix uses current_fps which is the dynamically-detected frame rate from stream NAL data

    How they work together:

    • --scc-framerate = explicit user-specified frame rate override
    • current_fps = auto-detected from stream (used by this fix)

    This ensures correct SCC timecodes when the stream frame rate is detected, while --scc-framerate remains available for explicit control.

    Note: As noted in the comments, industry convention is to use 29.97 for SCC regardless of source frame rate. This fix provides the same-as-media behavior for users who need it, while the default (when --scc-framerate is not used and stream fps is not detected) remains 29.97.

  7. Varadraj75 commented on Feb 27, 2026

    @Varadraj75
    ContributorAuthor

    Thanks @Atul-Chahar for the quick PR! Glad the issue was worth fixing.

    @bbgdzxng1 your breakdown of the --scc-framerate path vs the print_scc_time()
    path was really clarifying — good to know they're separate code paths and both
    now covered.

  8. bbgdzxng1 commented on Feb 27, 2026

    @bbgdzxng1

    @Atul-Chahar and @Varadraj75 I do love hate to nit-pick. What do people feel about updating the online help from...

    $ ccextractor --help | grep "scc-framerate"  
          --scc-framerate <fps>
              Set the frame rate for SCC (Scenarist Closed Caption) input files.
              Valid values: 29.97 (default), 24, 25, 30
              Example: --scc-framerate 25
    
    - Set the frame rate for SCC (Scenarist Closed Caption) input files.
    + Set the frame rate for Scenarist Closed Captioning (SCC) input and/or output encoding.
    

    ...given that "the --scc-framerate option now affects both input parsing AND output encoding"

    Just a thought / doc update. Feels like the command line help suggests it is input specific only, and thus potentially misleading given the #2146 update.

    Anyone got any better phrasing?

    And if we are I am getting really nit-picky, should 23.98 be an alias for 24 as an enumerated "Valid value"? 23.98 is notable by its absence, but 29.97 and 30 are both explicitly specified, but probably result in the same eventual timing, give or take a 1000/1001 frame sync?

    Feel free to ignore this pedant. I'm just a dumb user.

    • I am not a developer
    • CCExtractor rocks!
  9. Varadraj75 commented on Feb 28, 2026

    @Varadraj75
    ContributorAuthor

    Both are great points @bbgdzxng1 —

    On the help text, agree it's misleading. Something like:

      --scc-framerate <fps>
          Set the frame rate for Scenarist Closed Captioning (SCC) input parsing 
          and output encoding.
          Valid values: 23.98, 29.97 (default), 24, 25, 30
          Example: --scc-framerate 25
    

    On the 23.98 alias — definitely worth adding. 29.97 and 30 are both listed
    explicitly so 23.98 deserves the same treatment. Under the hood 23.98 = 24000/1001
    just as 29.97 = 30000/1001, so it's a natural pair.

    I can put together a small follow-up PR covering both — updated help text and
    23.98 as a valid --scc-framerate value. Would that be welcome?

  10. bbgdzxng1 commented on Feb 28, 2026

    @bbgdzxng1

    @Varadraj75, from an end-user perspective, I think your proposal in the PR covers both nits. Thank-you.

    ✅ Good catch on "23.98" | "23.976" too.

  11. Varadraj75 commented on Feb 28, 2026

    @Varadraj75
    ContributorAuthor

    #2150 has been merged, covering the two follow-up improvements raised by
    @bbgdzxng1 — 23.98 as a valid --scc-framerate alias and clarified help text.

    The original fix (replacing hardcoded 29.97 with current_fps in
    print_scc_time()) is tracked in #2146. I'll close this issue once that merges.

  12. added a commit that references this issue on Mar 3, 2026
    3738e3a
  13. NaitikVerma6776 commented on Oct 8, 2026

    @NaitikVerma6776
    Contributor

    @cfsmp3 I looked into this issue and found that the hardcoded 29.97 is still present on the current master.
    I’d like to work on this, but before starting I wanted to confirm the expected behaviour around current_fps vs --scc-framerate.
    Would it be okay if I take this up and work on the fix once the expected precedence is confirmed?

  14. harshalsp07 commented on Oct 20, 2026

    @harshalsp07
    Contributor

    Opened #2406. It uses the detected current_fps in the C and Rust 708 SCC writers, which is what the review on #2146 asked for. --scc-framerate is unchanged and still covers the 608 encoder and SCC input parsing. The PR has a 25 fps sample and the before/after SCC output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions