"""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)