"""End-to-end routing of --detail through watch.py on a local clip.""" from __future__ import annotations import os import subprocess import sys from pathlib import Path WATCH = Path(__file__).resolve().parent.parent / "skills" / "watch" / "scripts" / "watch.py" def _run(clip: Path, *args: str, env_extra: dict | None = None) -> str: env = dict(os.environ) env.pop("WATCH_DETAIL", None) if env_extra: env.update(env_extra) proc = subprocess.run( [sys.executable, str(WATCH), str(clip), "--no-whisper", *args], capture_output=True, text=True, env=env, ) assert proc.returncode == 0, proc.stderr return proc.stdout def test_efficient_uses_keyframe_engine(cut_clip: Path): out = _run(cut_clip, "--detail", "efficient") assert "(keyframe" in out assert "**Detail:** efficient" in out def test_balanced_uses_scene_engine(cut_clip: Path): out = _run(cut_clip, "--detail", "balanced") assert "(scene" in out assert "**Detail:** balanced" in out def test_token_burner_uses_scene_engine(cut_clip: Path): out = _run(cut_clip, "--detail", "token-burner") assert "(scene" in out def test_transcript_skips_frames(cut_clip: Path): out = _run(cut_clip, "--detail", "transcript") assert "skipped" in out assert "frame_0000.jpg" not in out def test_flag_overrides_env(cut_clip: Path): out = _run(cut_clip, "--detail", "efficient", env_extra={"WATCH_DETAIL": "balanced"}) assert "(keyframe" in out def test_default_is_balanced(cut_clip: Path): out = _run(cut_clip) # no flag, WATCH_DETAIL cleared assert "**Detail:** balanced" in out assert "(scene" in out def test_timestamps_add_cue_frames_to_detail(cut_clip: Path): out = _run(cut_clip, "--detail", "balanced", "--timestamps", "1,3") assert "reason=transcript-cue" in out assert "reason=scene-change" in out # detail frames still present (additive) def test_timestamps_with_transcript_detail_is_cue_only(cut_clip: Path): out = _run(cut_clip, "--detail", "transcript", "--timestamps", "1,3") assert "reason=transcript-cue" in out assert "reason=scene-change" not in out assert "reason=keyframe" not in out def _frame_lines(out: str) -> int: return sum(1 for line in out.splitlines() if "/frames/frame_" in line.replace("\\", "/") and "(t=" in line) def test_dedup_collapses_static_by_default(static_clip: Path): out = _run(static_clip) # solid blue → identical frames collapse to one assert "near-duplicate" in out assert _frame_lines(out) == 1 def test_no_dedup_preserves_static_frames(static_clip: Path): out = _run(static_clip, "--no-dedup") assert "near-duplicate" not in out assert _frame_lines(out) > 1 import pytest import watch from transcribe import Segments def _caption_context(monkeypatch, tmp_path, body): subtitle = tmp_path / 'captions.vtt' subtitle.write_text('WEBVTT\n\n' + body) monkeypatch.setattr(watch, 'fetch_captions', lambda *a, **kw: { 'subtitle_path': str(subtitle), 'info': {'title': '안녕', 'duration': 30}, 'caption_track': {'language': 'ko', 'kind': 'manual', 'provenance': 'original'}, }) return subtitle def test_silent_focus_never_calls_asr(monkeypatch, tmp_path, capsys): _caption_context(monkeypatch, tmp_path, '00:00.000 --> 00:10.000\nhello\n') monkeypatch.setattr(watch, 'download', lambda *a, **kw: pytest.fail('caption-only focus must not download')) monkeypatch.setattr(watch, 'transcribe_video', lambda *a, **kw: pytest.fail('a silent focus is not a missing track')) monkeypatch.setattr(sys, 'argv', ['watch', 'https://example.com/video', '--detail', 'transcript', '--start', '20', '--end', '30']) assert watch.main() == 0 assert 'no speech in selected range' in capsys.readouterr().out def test_good_captions_survive_media_failure(monkeypatch, tmp_path, capsys): _caption_context(monkeypatch, tmp_path, '00:00.000 --> 00:10.000\nhello\n') def fail(*a, **kw): raise SystemExit('blocked download') monkeypatch.setattr(watch, 'download', fail) monkeypatch.setattr(sys, 'argv', ['watch', 'https://example.com/video']) assert watch.main() == 0 out = capsys.readouterr().out assert 'hello' in out and 'Media unavailable' in out and 'ko, manual, original' in out def test_good_captions_survive_probe_denial(monkeypatch, tmp_path, capsys): _caption_context(monkeypatch, tmp_path, '00:00.000 --> 00:10.000\nhello\n') monkeypatch.setattr(watch, 'download', lambda *a, **kw: {'video_path': 'media.mp4'}) def fail(*a): raise SystemExit('Cannot run ffprobe (PermissionError)') monkeypatch.setattr(watch, 'get_metadata', fail) monkeypatch.setattr(sys, 'argv', ['watch', 'https://example.com/video']) assert watch.main() == 0 out = capsys.readouterr().out assert 'hello' in out and 'PermissionError' in out def _mock_audio(monkeypatch): monkeypatch.setattr(watch, 'download', lambda *a, **kw: {'video_path': 'video.mp4'}) monkeypatch.setattr(watch, 'get_metadata', lambda *a: {'duration_seconds': 30, 'has_audio': True, 'has_video': False}) def test_partial_report_includes_missing_intervals(monkeypatch, capsys): _mock_audio(monkeypatch) monkeypatch.setattr(watch, 'load_api_key', lambda *a: ('groq', 'dummy')) monkeypatch.setattr(watch, 'transcribe_video', lambda *a, **kw: (Segments([{'start': 1, 'end': 2, 'text': 'good'}], gaps=[{'start': 10, 'end': 20}]), 'groq')) monkeypatch.setattr(sys, 'argv', ['watch', 'video.mp4', '--detail', 'transcript']) assert watch.main() == 0 out = capsys.readouterr().out assert 'partial; missing intervals' in out and '00:10 → 00:20' in out def test_no_whisper_disables_configured_local_backend(monkeypatch): _mock_audio(monkeypatch) monkeypatch.setenv('WATCH_WHISPER_BACKEND', 'whisperx') monkeypatch.setattr(watch, 'transcribe_video', lambda *a, **kw: pytest.fail('fallback disabled')) monkeypatch.setattr(sys, 'argv', ['watch', 'video.mp4', '--detail', 'transcript', '--no-whisper']) assert watch.main() == 0 def test_local_failure_does_not_resolve_cloud(monkeypatch, capsys): _mock_audio(monkeypatch) monkeypatch.setattr(watch, 'load_api_key', lambda *a: pytest.fail('no cloud fallback')) def fail(*a, **kw): assert kw['backend'] == 'whisperx' raise SystemExit('local model failed') monkeypatch.setattr(watch, 'transcribe_video', fail) monkeypatch.setattr(sys, 'argv', ['watch', 'video.mp4', '--detail', 'transcript', '--whisper', 'whisperx']) assert watch.main() == 1 assert 'local model failed' in capsys.readouterr().out def test_contradictory_flags_rejected(monkeypatch): monkeypatch.setattr(sys, 'argv', ['watch', 'video.mp4', '--no-whisper', '--whisper', 'whisperx']) with pytest.raises(SystemExit): watch.main() def test_user_out_dir_and_local_source_preserved(static_clip, tmp_path): source = tmp_path / 'source.mp4' source.write_bytes(static_clip.read_bytes()) sentinel = tmp_path / 'keep.txt' sentinel.write_text('keep') _run(source, '--out-dir', str(tmp_path)) assert source.read_bytes() == static_clip.read_bytes() and sentinel.read_text() == 'keep' assert list(tmp_path.glob('watch-*/frames/frame_*.jpg')) def test_cli_no_whisper_overrides_invalid_saved_backend(monkeypatch): _mock_audio(monkeypatch) monkeypatch.setenv('WATCH_WHISPER_BACKEND', 'invalid-old-preference') monkeypatch.setattr(sys, 'argv', ['watch', 'video.mp4', '--detail', 'transcript', '--no-whisper']) assert watch.main() == 0