diff --git a/lib/voice.py b/lib/voice.py new file mode 100644 index 0000000..163fac4 --- /dev/null +++ b/lib/voice.py @@ -0,0 +1,36 @@ +"""Resolve a ComfyUI AUDIO input or string path into a voice-reference dict.""" + +import logging +import os +from typing import Optional + +from lib.audio import audio_dict_to_wav + +log = logging.getLogger(__name__) + + +def build_voice_reference( + audio: Optional[dict], + audio_path: str, + temp_dir: str, +) -> dict: + """Return a voice-reference container. + + Priority: + 1. If `audio` is set, write it to a temp WAV and use that path. + If `audio_path` is also set, log a warning and ignore it. + 2. Else if `audio_path` is non-empty, validate the path exists and use it. + 3. Else return source="none" / path=None (default voice). + """ + if audio is not None: + if audio_path: + log.warning("Both audio and audio_path provided; ignoring audio_path=%r", audio_path) + path = audio_dict_to_wav(audio, dest_dir=temp_dir) + return {"path": path, "source": "audio"} + + if audio_path: + if not os.path.isfile(audio_path): + raise FileNotFoundError(f"Voice reference audio not found: {audio_path}") + return {"path": audio_path, "source": "path"} + + return {"path": None, "source": "none"} diff --git a/tests/test_voice.py b/tests/test_voice.py new file mode 100644 index 0000000..66f2898 --- /dev/null +++ b/tests/test_voice.py @@ -0,0 +1,44 @@ +import os +import pytest +from lib.voice import build_voice_reference + + +def test_no_inputs_returns_none_source(tmp_path): + result = build_voice_reference(audio=None, audio_path="", temp_dir=str(tmp_path)) + assert result == {"path": None, "source": "none"} + + +def test_audio_path_only(tmp_path, sample_comfy_audio): + # Write a fake wav so the path exists + import torchaudio + wav_path = tmp_path / "ref.wav" + torchaudio.save(str(wav_path), sample_comfy_audio["waveform"][0], sample_comfy_audio["sample_rate"]) + result = build_voice_reference(audio=None, audio_path=str(wav_path), temp_dir=str(tmp_path)) + assert result["source"] == "path" + assert result["path"] == str(wav_path) + + +def test_audio_path_missing_raises(tmp_path): + with pytest.raises(FileNotFoundError): + build_voice_reference(audio=None, audio_path=str(tmp_path / "nope.wav"), temp_dir=str(tmp_path)) + + +def test_audio_input_writes_temp_wav(tmp_path, sample_comfy_audio): + result = build_voice_reference(audio=sample_comfy_audio, audio_path="", temp_dir=str(tmp_path)) + assert result["source"] == "audio" + assert os.path.isfile(result["path"]) + assert result["path"].startswith(str(tmp_path)) + + +def test_audio_wins_over_path(tmp_path, sample_comfy_audio, caplog): + import torchaudio + wav_path = tmp_path / "ref.wav" + torchaudio.save(str(wav_path), sample_comfy_audio["waveform"][0], sample_comfy_audio["sample_rate"]) + result = build_voice_reference( + audio=sample_comfy_audio, + audio_path=str(wav_path), + temp_dir=str(tmp_path), + ) + assert result["source"] == "audio" + assert result["path"] != str(wav_path) + assert any("audio_path" in rec.message for rec in caplog.records)