fix: exclude WebVTT metadata before scanning cue timestamps
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user