affaan-m/ECC · error · ValueError

bad timecode

Error message

bad timecode {tc!r}

What it means

_tc_to_frames parses an SMPTE-style timecode string with _TC_RE and raises ValueError when the string does not match the expected HH:MM:SS:FF pattern. It is used by parse_edl to convert EDL timecodes into frame counts.

Solutions

  1. Open the EDL and fix the malformed timecode to match the expected format (e.g. 'HH:MM:SS:FF')
  2. Check _TC_RE in export.py to confirm the exact accepted pattern including drop-frame ';' separators
  3. Pre-clean source EDLs from non-standard exporters or re-export with standard timecode notation

Example fix

// before
edl = parse_edl("clip.tc = '1:23:45:6'")  # single-digit fields
// after
edl = parse_edl("clip.tc = '01:23:45:06'")  # zero-padded HH:MM:SS:FF
Defensive patterns

Strategy: validation

Validate before calling

import re
_TC = re.compile(r"^\d{2}:\d{2}:\d{2}[:;]\d{2}$")
assert _TC.match(tc), f"malformed timecode: {tc!r}"

Type guard

def is_valid_timecode(tc) -> bool:
    return isinstance(tc, str) and bool(_TC_RE.match(tc))

Try / catch

try:
    frames = _tc_to_frames(tc, fps, drop)
except ValueError as e:
    print(f"EDL line has bad timecode: {e}; fix the source EDL")
    continue

Prevention

When it happens

Trigger: parse_edl encounters a timecode field that is not in the expected format, e.g. '1:23:45:06' (single-digit hour if the regex requires two), '00:00:00;05' with a semicolon if the regex only accepts colons, or a truncated/garbage value like 'N/A'.

Common situations: Hand-edited EDL files, exported EDLs from NLEs using drop-frame semicolon notation or different field separators, and blank timecode cells in sparse EDLs.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/72f73b6f49be905f. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/export.py:246

# ---------------------------------------------------------------------------
# EDL parsing (round-trip validation)
# ---------------------------------------------------------------------------

_TC_RE = re.compile(r"^(\d{2}):(\d{2}):(\d{2})[:;](\d{2})$")
_EVENT_RE = re.compile(
    r"^(\d+)\s+(\S+)\s+V\s+C\s+"
    r"(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+"
    r"(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+"
    r"(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+"
    r"(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*$"
)


def _tc_to_frames(tc: str, fps: float | Fraction, drop: bool) -> int:
    m = _TC_RE.match(tc)
    if not m:
        raise ValueError(f"bad timecode {tc!r}")
    hh, mm, ss, ff = (int(g) for g in m.groups())
    rate = int(round(float(fps_fraction(fps))))
    displayed = (hh * 3600 + mm * 60 + ss) * rate + ff
    if drop:
        dropped = int(round(float(fps_fraction(fps)) * 0.066666))
        total_minutes = hh * 60 + mm
        # Skipped frame numbers before this TC: 2/min except every 10th min.
        skipped = dropped * (total_minutes - total_minutes // 10)
        return displayed - skipped
    return displayed


def parse_edl(text: str, fps: float = 24.0) -> list[dict]:
    """Parse a CMX3600 EDL back into structured events (round-trip check)."""
    drop = "FCM: DROP FRAME" in text
    events: list[dict] = []
    for line in text.splitlines():
        m = _EVENT_RE.match(line)

View on GitHub (pinned to 8321021c54)