Files
gart/code/tests/test_subtitle_overlap_regression.py
T
João HenriqueandClaude Sonnet 5 0fdfe33613 fix(legendas): impede legenda comum sob composição dinâmica e duplicação ao regerar
Marca cada título gerado (dynamic/plain) em metadata para que regenerar
substitua a saída anterior em vez de empilhar, e usa os spans de ênfase
revisados (não os segmentos brutos do Whisper) como janela da composição
dinâmica, evitando que ela invada o trecho de legenda comum seguinte.
suppress_plain_under_dynamic corta qualquer sobra visível como rede de
segurança.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 21:51:02 -04:00

73 lines
3.2 KiB
Python

"""Exercise the real subtitle handler across emphasis gaps and regeneration."""
import asyncio
from pathlib import Path
import pytest
from fcpxml.models import DynamicSubtitleConfig
from fcpxml.writer import FCPXMLModifier
from server_tools import subtitles
@pytest.fixture
def caption_job(tmp_path, monkeypatch):
modifier = FCPXMLModifier(Path(__file__).parents[1] / "examples/sample.fcpxml")
parent = modifier._require_clip("Interview_A")
parent.set("start", "0s")
parent.set("duration", "10s")
media = tmp_path / "speech.mov"
media.touch()
modifier.resources[parent.get("ref")]["src"] = media.as_uri()
monkeypatch.setattr(modifier, "_iter_spine_clips", lambda: iter([(0, parent)]))
monkeypatch.setattr(subtitles, "_setup_modifier", lambda *args: (
"input.fcpxml", str(tmp_path / "output.fcpxml"), modifier,
))
monkeypatch.setattr(subtitles, "get_active_dynamic_subtitle_layouts", lambda: [
{"role": "titles.dinamicas"},
])
monkeypatch.setattr(subtitles, "_build_dynamic_subtitle_config", lambda *args: DynamicSubtitleConfig())
monkeypatch.setattr(subtitles, "load_plain_subtitle_config", lambda: {
"role": "titles.convencionais", "font": "Helvetica Neue", "font_size": 48,
"font_color": "1 1 1 1", "max_words": 1, "position_y": -100,
"uppercase": False, "keep_punctuation": True,
})
monkeypatch.setattr(subtitles, "_load_plain_exclude_spans", lambda _: [])
monkeypatch.setattr(subtitles, "_load_emphasis_spans", lambda _: [
{"start": 0, "end": 1}, {"start": 4, "end": 5},
])
monkeypatch.setattr(subtitles, "_load_or_transcribe", lambda *args: ({
"words": [
{"word": "corpo", "start": 0, "end": 1},
{"word": "muda", "start": 2, "end": 3},
{"word": "também", "start": 4, "end": 5},
],
"segments": [{"start": 0, "end": 1}, {"start": 4, "end": 5}],
}, None))
return modifier, parent
def test_dynamic_composition_clears_before_plain_words_in_emphasis_gap(caption_job):
modifier, parent = caption_job
asyncio.run(subtitles.handle_generate_subtitles_by_emphasis({}))
dynamic = parent.findall("ref-clip")
plain = parent.findall("title")
assert dynamic and plain
for composition in dynamic:
start = modifier._parse_time(composition.get("offset"))
end = start + modifier._parse_time(composition.get("duration"))
for title in plain:
plain_start = modifier._parse_time(title.get("offset"))
plain_end = plain_start + modifier._parse_time(title.get("duration"))
assert not (start < plain_end and plain_start < end), "dynamic and plain overlap"
def test_regeneration_replaces_generated_subtitles_and_preserves_manual_titles(caption_job):
modifier, parent = caption_job
manual = modifier.add_text_title(parent, "Nome da médica", role="titles.manual")
asyncio.run(subtitles.handle_generate_subtitles_by_emphasis({}))
initial = len(parent.findall("ref-clip")) + len(parent.findall("title"))
asyncio.run(subtitles.handle_generate_subtitles_by_emphasis({}))
assert len(parent.findall("ref-clip")) + len(parent.findall("title")) == initial
assert manual in list(parent)