Merge branch 'fix/review-2077' into fix/community-integration

# Conflicts:
#	CHANGELOG.md
#	docs/dubbing/translation-engines.md
This commit is contained in:
Palash Debnath
2026-09-17 13:57:07 +05:30
4 changed files with 29 additions and 4 deletions
+3
View File
@@ -41,6 +41,9 @@ the frozen-backend fallback mirror it for their toolchains.
- Handle missing Electron signing credentials and retry packaging fixes without moving release tags (#2157)
- Electron packaging can recover without changing a release tag
- Parse pasted WebVTT cues while separating metadata, identifiers, empty cues, and complete timing lines (#2077) — thanks @kevin9327!
## [0.5.3] — 2026-09-17
**Highlights**
+9 -3
View File
@@ -86,8 +86,14 @@ def parse_srt(content: str) -> SrtParseResult:
# 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):
lines = block.strip("\n").split("\n")
first = lines[0].strip()
# WebVTT's block parser gives a timing line in position two
# precedence over the identifier (including STYLE/REGION/NOTE).
# https://www.w3.org/TR/webvtt1/#file-parsing
identifies_cue = len(lines) > 1 and _TIMING_RE.match(lines[1])
metadata = first in {"STYLE", "REGION"} or re.match(r"NOTE(?:[ \t]|$)", first)
if metadata and not identifies_cue:
continue
blocks.append(block)
text = "\n\n".join(blocks)
@@ -110,7 +116,7 @@ def parse_srt(content: str) -> SrtParseResult:
if is_webvtt:
# The blank separator ends WebVTT dialogue; following identifiers,
# NOTE/STYLE blocks belong outside the cue, even when numeric.
body = re.split(r"\n[^\S\n]*\n", body.lstrip("\n"), maxsplit=1)[0]
body = re.split(r"\n[^\S\n]*\n", body, maxsplit=1)[0]
# An index must directly precede the next timing line. A blank line
# AFTER a number instead marks that number as preceding dialogue.
next_index = None
+2
View File
@@ -263,3 +263,5 @@ Mixed or malformed SRT files can make a bare number indistinguishable from spoke
If the Argos native runtime cannot load, both desktop and browser clients show localized recovery guidance: reinstall the backend or select NLLB. The API returns the stable `argos_runtime_unavailable` error code without exposing native library paths.
WebVTT import excludes NOTE, STYLE, and REGION metadata blocks before parsing timestamps; timing examples in notes never become spoken segments.
WebVTT import separates metadata blocks from cue identifiers using the [WebVTT block-parsing rules](https://www.w3.org/TR/webvtt1/#file-parsing): a timing line immediately after an identifier makes a cue, even when that identifier is NOTE, STYLE, or REGION. Later timing examples inside metadata are ignored, and empty cues never borrow the next cues identifier as dialogue.
+15 -1
View File
@@ -375,7 +375,7 @@ def test_numeric_only_cue_after_invalid_cue_is_not_discarded():
@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'
text = f'WEBVTT\n\n{header}\nMetadata content\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
@@ -384,3 +384,17 @@ def test_webvtt_metadata_timestamps_never_become_dialogue(header):
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'
@pytest.mark.parametrize('identifier', ['STYLE', 'REGION', 'NOTE', 'NOTE identifier'])
def test_webvtt_metadata_words_can_identify_a_cue(identifier):
text = f'WEBVTT\n\n{identifier}\n00:01.000 --> 00:02.000\nSpoken text\n'
assert [cue['text'] for cue in parse_srt(text).segments] == ['Spoken text']
@pytest.mark.parametrize('gap', ['\n', ' \n', '\n\n'])
def test_empty_webvtt_cue_does_not_capture_following_identifier(gap):
text = f'WEBVTT\n\n00:01.000 --> 00:02.000\n{gap}next-id\n00:03.000 --> 00:04.000\nSpoken text\n'
result = parse_srt(text)
assert result.skipped_cues == 1
assert [cue['text'] for cue in result.segments] == ['Spoken text']