fix: exclude WebVTT metadata before scanning cue timestamps

This commit is contained in:
Palash Debnath
2026-09-17 13:48:45 +05:30
parent fb80d88f34
commit 308dda0cac
3 changed files with 25 additions and 13 deletions
+10 -13
View File
@@ -56,19 +56,6 @@ def _is_index_line(line: str) -> bool:
return stripped.isascii() and stripped.isdigit()
def _uses_index_lines(text: str, first_timing_start: int) -> bool:
"""Initial numbering hint for lenient files without blank separators.
Later cue boundaries are also inspected: mixed indexed/unindexed files
must not leak index lines or delete numeric dialogue.
"""
head = text[:first_timing_start]
for line in reversed(head.split("\n")):
if line.strip():
return _is_index_line(line)
return False
@dataclass
class SrtParseResult:
segments: list[dict]
@@ -94,6 +81,16 @@ def parse_srt(content: str) -> SrtParseResult:
text = content.lstrip("").replace("\r\n", "\n").replace("\r", "\n")
is_webvtt = bool(re.match(r"WEBVTT(?:[ \t]|\n|$)", text.lstrip()))
if is_webvtt:
# Metadata is block-scoped. Filter it BEFORE scanning timings so an
# example timestamp inside a NOTE/STYLE/REGION cannot become speech.
blocks = []
for block in re.split(r"\n[^\S\n]*\n", text):
first = block.strip().split("\n", 1)[0].strip()
if first in {"STYLE", "REGION"} or re.match(r"NOTE(?:[ \t]|$)", first):
continue
blocks.append(block)
text = "\n\n".join(blocks)
raw: list[dict] = []
skipped = 0
# Find every timing line, slice the cue text from there to the next
+2
View File
@@ -240,3 +240,5 @@ Paste translation accepts WebVTT files with hourless timestamps. Only timing rec
Subtitle import preserves numeric dialogue such as years and countdowns, including files mixing numbered and unnumbered cues. Cue numbers are removed only at identified cue boundaries.
Mixed or malformed SRT files can make a bare number indistinguishable from spoken dialogue. The importer removes numbering only when cue boundaries and sequential numbering support it; ambiguous nonsequential numbers are retained as text to avoid silent data loss. Standard indexed SRT and WebVTT exports avoid this ambiguity.
WebVTT import excludes NOTE, STYLE, and REGION metadata blocks before parsing timestamps; timing examples in notes never become spoken segments.
+13
View File
@@ -371,3 +371,16 @@ def test_skipped_cue_still_advances_numbering_state():
def test_numeric_only_cue_after_invalid_cue_is_not_discarded():
text = "1\n00:00:01,000 --> 00:00:02,000\nFirst\n\n2\n00:00:04,000 --> 00:00:03,000\nInvalid\n\n3\n00:00:05,000 --> 00:00:06,000\n4\n00:00:07,000 --> 00:00:08,000\nLast"
assert [cue["text"] for cue in parse_srt(text).segments] == ["First", "4", "Last"]
@pytest.mark.parametrize('header', ['NOTE', 'NOTE translator notes', 'NOTE\ttranslator notes', 'STYLE', 'REGION'])
def test_webvtt_metadata_timestamps_never_become_dialogue(header):
text = f'WEBVTT\n\n{header}\n00:00.000 --> 00:02.000\nMetadata only\n\ncue\n00:03.000 --> 00:04.000\nReal dialogue\n'
result = parse_srt(text)
assert [cue['text'] for cue in result.segments] == ['Real dialogue']
assert result.segments[0]['start'] == 3
def test_webvtt_note_words_inside_dialogue_are_retained():
text = 'WEBVTT\n\n00:01.000 --> 00:02.000\nNOTE this is spoken\nSTYLE\nREGION\n'
assert parse_srt(text).segments[0]['text'] == 'NOTE this is spoken\nSTYLE\nREGION'