testsAndMisc/linux_configuration/tests/test_transcribe_diarize_audio.py
Krzysztof kuhy Rudnicki dbf2f29b00 _transcribe_diarize: split 356 -> 164 + 143 + 105
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
2026-08-17 13:59:39 +02:00

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)