mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
The last Python file over the cap:
_transcribe_media.py 143 _try_import, both duration probes,
get_media_duration, ffmpeg transcode, cleanup
_transcribe_cluster.py 105 _resample_linear, _kmeans_cosine and its
iteration loop
_transcribe_diarize.py 164 the pipeline: audio loading, the classifier,
embedding extraction, diarize_segments
The graph is a tree: _transcribe_media imports nothing local, _cluster takes
only _try_import from it, _diarize imports from both. All 13 top-level
definitions verified AST-identical to HEAD, and an AST scan confirms the
three module constants travelled to the modules that use them.
transcribe_fw.py now imports get_media_duration from _transcribe_media
rather than through _transcribe_diarize. Ruff removed that re-export as
unused and broke the import; running the program is what caught it, and
naming the owning module is the correct fix under the no-barrels rule.
Verified: transcribe_fw.py /nonexistent.wav still exits 2 and --help exits
0; 63 tests pass at 100% line and branch on all three modules; 338 pass in
the directory. Over-cap files 33 -> 32, and no Python file is over the cap
any more - the remaining 32 are 29 shell, 2 kotlin, 1 dart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
"""Tests for the ffmpeg transcode fallback, temp files, and audio loading.
|
|
|
|
``_load_audio`` reads with soundfile and, when that fails, transcodes through
|
|
ffmpeg to a temporary 16 kHz mono WAV. Both binaries are patched, so these
|
|
pass whether or not ffmpeg is installed on the machine running them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import shutil
|
|
import subprocess
|
|
from typing import TYPE_CHECKING
|
|
|
|
import _transcribe_diarize as diar
|
|
import _transcribe_media as media
|
|
import pytest
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def _importer(available: dict[str, object]) -> object:
|
|
"""Return a _try_import double resolving only the named modules."""
|
|
|
|
def _try(name: str) -> object | None:
|
|
return available.get(name)
|
|
|
|
return _try
|
|
|
|
|
|
def _only(name: str, module: object) -> object:
|
|
"""Return a _try_import double resolving exactly one module name."""
|
|
|
|
def _try(requested: str) -> object | None:
|
|
return module if requested == name else None
|
|
|
|
return _try
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# _ffmpeg_transcode_to_wav16_mono / _cleanup_temp
|
|
# --------------------------------------------------------------------------- #
|
|
def test_transcode_returns_none_without_ffmpeg(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""No ffmpeg binary means no fallback transcode."""
|
|
monkeypatch.setattr(shutil, "which", lambda _b: None)
|
|
|
|
assert media._ffmpeg_transcode_to_wav16_mono("clip.mp4") is None
|
|
|
|
|
|
def test_transcode_returns_the_temp_path_on_success(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A successful transcode hands back the temporary WAV path."""
|
|
monkeypatch.setattr(shutil, "which", lambda _b: "/usr/bin/ffmpeg")
|
|
commands: list[list[str]] = []
|
|
|
|
def _record(cmd: list[str], **_kwargs: object) -> None:
|
|
commands.append(cmd)
|
|
|
|
monkeypatch.setattr(subprocess, "run", _record)
|
|
|
|
result = media._ffmpeg_transcode_to_wav16_mono("clip.mp4")
|
|
|
|
assert result is not None
|
|
assert result.endswith(".wav")
|
|
assert "16000" in commands[0]
|
|
assert "-ac" in commands[0]
|
|
media._cleanup_temp(result)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"error", [OSError("boom"), subprocess.CalledProcessError(1, "ffmpeg")]
|
|
)
|
|
def test_transcode_cleans_up_after_a_failure(
|
|
monkeypatch: pytest.MonkeyPatch, error: Exception
|
|
) -> None:
|
|
"""A failed transcode removes the temp file it had already created."""
|
|
monkeypatch.setattr(shutil, "which", lambda _b: "/usr/bin/ffmpeg")
|
|
removed: list[str] = []
|
|
monkeypatch.setattr(media.Path, "unlink", lambda self: removed.append(str(self)))
|
|
|
|
def _boom(*_a: object, **_k: object) -> None:
|
|
raise error
|
|
|
|
monkeypatch.setattr(subprocess, "run", _boom)
|
|
|
|
assert media._ffmpeg_transcode_to_wav16_mono("clip.mp4") is None
|
|
assert len(removed) == 1
|
|
|
|
|
|
def test_cleanup_temp_removes_an_existing_file(tmp_path: Path) -> None:
|
|
"""The temp WAV is deleted once diarization is done with it."""
|
|
victim = tmp_path / "scratch.wav"
|
|
victim.write_bytes(b"RIFF")
|
|
|
|
media._cleanup_temp(str(victim))
|
|
|
|
assert not victim.exists()
|
|
|
|
|
|
def test_cleanup_temp_ignores_none() -> None:
|
|
"""No temp file was created, so there is nothing to remove."""
|
|
media._cleanup_temp(None)
|
|
|
|
|
|
def test_cleanup_temp_survives_an_already_deleted_file(tmp_path: Path) -> None:
|
|
"""A file removed by something else is not an error."""
|
|
media._cleanup_temp(str(tmp_path / "never-existed.wav"))
|
|
|
|
|
|
def test_load_audio_returns_none_without_soundfile(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Diarization needs soundfile; without it there is no audio to read."""
|
|
monkeypatch.setattr(diar, "_try_import", lambda _n: None)
|
|
|
|
assert diar._load_audio("clip.wav") is None
|
|
|
|
|
|
def test_load_audio_warns_when_the_ffmpeg_fallback_is_unavailable(
|
|
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""An unreadable file with no transcode path warns and gives up."""
|
|
|
|
class _Sf:
|
|
def read(self, *_a: object, **_k: object) -> None:
|
|
msg = "unsupported format"
|
|
raise OSError(msg)
|
|
|
|
monkeypatch.setattr(diar, "_try_import", _only("soundfile", _Sf()))
|
|
monkeypatch.setattr(diar, "_ffmpeg_transcode_to_wav16_mono", lambda _p: None)
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
assert diar._load_audio("clip.mp4") is None
|
|
|
|
assert "no ffmpeg fallback" in caplog.text
|
|
|
|
|
|
class _SfFailingThenOk:
|
|
"""A soundfile double that fails on the original and reads the transcode."""
|
|
|
|
def __init__(self, *, second_fails: bool = False) -> None:
|
|
self.calls: list[str] = []
|
|
self._second_fails = second_fails
|
|
|
|
def read(self, path: str, **_kwargs: object) -> tuple[str, int]:
|
|
"""Fail for the source file; succeed (or not) for the transcode."""
|
|
self.calls.append(path)
|
|
if len(self.calls) == 1:
|
|
msg = "unsupported format"
|
|
raise OSError(msg)
|
|
if self._second_fails:
|
|
msg = "still unreadable"
|
|
raise OSError(msg)
|
|
return "wav-data", 16000
|
|
|
|
|
|
def test_load_audio_falls_back_to_a_transcoded_copy(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
"""An unreadable container is transcoded, then read from the temp WAV."""
|
|
scratch = str(tmp_path / "scratch.wav")
|
|
sf = _SfFailingThenOk()
|
|
monkeypatch.setattr(diar, "_try_import", _only("soundfile", sf))
|
|
monkeypatch.setattr(diar, "_ffmpeg_transcode_to_wav16_mono", lambda _p: scratch)
|
|
|
|
result = diar._load_audio("clip.mp4")
|
|
|
|
assert result == ("wav-data", 16000, scratch)
|
|
assert sf.calls == ["clip.mp4", scratch]
|
|
|
|
|
|
def test_load_audio_cleans_up_when_the_transcode_is_also_unreadable(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""If even the transcode cannot be read, the temp file is removed."""
|
|
temp = tmp_path / "scratch.wav"
|
|
temp.write_bytes(b"RIFF")
|
|
monkeypatch.setattr(
|
|
diar, "_try_import", _only("soundfile", _SfFailingThenOk(second_fails=True))
|
|
)
|
|
monkeypatch.setattr(diar, "_ffmpeg_transcode_to_wav16_mono", lambda _p: str(temp))
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
assert diar._load_audio("clip.mp4") is None
|
|
|
|
assert "Could not read transcoded audio" in caplog.text
|
|
assert not temp.exists()
|
|
|
|
|
|
def test_load_audio_returns_the_source_when_it_reads_cleanly(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A readable file needs no transcode and reports no temp path."""
|
|
|
|
class _Sf:
|
|
def read(self, _path: str, **_kwargs: object) -> tuple[str, int]:
|
|
return "wav-data", 44100
|
|
|
|
monkeypatch.setattr(diar, "_try_import", _only("soundfile", _Sf()))
|
|
|
|
assert diar._load_audio("clip.wav") == ("wav-data", 44100, None)
|