Moxie-Multimedia / tests /test_speech.py
turtle89431's picture
Upload folder using huggingface_hub (part 2)
3afd6d6 verified
Raw History Blame Contribute Delete
9.02 kB
from pathlib import Path
import sys
import types
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from utils import speech
def _install_fake_torch(monkeypatch, *, cuda_available: bool = False, mps_available: bool = False):
class FakeCuda:
@staticmethod
def is_available():
return cuda_available
class FakeMpsBackend:
@staticmethod
def is_available():
return mps_available
class FakeBackends:
mps = FakeMpsBackend()
fake_torch = types.SimpleNamespace(cuda=FakeCuda(), backends=FakeBackends())
monkeypatch.setitem(sys.modules, "torch", fake_torch)
def test_requires_voxcpm_dependency(monkeypatch):
monkeypatch.setattr(speech.importlib.util, "find_spec", lambda name: None if name == "voxcpm" else object())
assert speech.missing_speech_dependencies() == ["voxcpm"]
def test_requires_voxcpm2_model_directory(tmp_path, monkeypatch):
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path))
monkeypatch.setattr(speech, "notify_missing_model", lambda model_name: {"name": model_name})
with pytest.raises(speech.MissingEasyMediaModelError):
speech.require_voxcpm2_model_dir()
def test_generate_voxcpm2_speech_passes_cfg_steps_normalize_and_reference(tmp_path, monkeypatch):
model_dir = tmp_path / "models" / "voxcpm" / "VoxCPM2"
model_dir.mkdir(parents=True)
output_dir = tmp_path / "output"
reference = tmp_path / "voice.wav"
reference.write_bytes(b"ref")
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path / "models"))
monkeypatch.setattr(speech.folder_paths, "get_output_directory", lambda: str(output_dir))
monkeypatch.setattr(speech, "missing_speech_dependencies", lambda: [])
_install_fake_torch(monkeypatch)
calls = {}
cleaned = []
class FakeVoxCPM:
@classmethod
def from_pretrained(cls, path, **kwargs):
calls["model_path"] = path
calls["model_kwargs"] = kwargs
return cls()
def generate(self, **kwargs):
calls["generate"] = kwargs
return 16000, [0.0, 0.1]
voxcpm_module = types.ModuleType("voxcpm")
voxcpm_module.VoxCPM = FakeVoxCPM
monkeypatch.setitem(sys.modules, "voxcpm", voxcpm_module)
monkeypatch.setattr(speech, "_write_audio_file", lambda path, audio, sample_rate: path.write_bytes(b"wav"))
monkeypatch.setattr(speech, "cleanup_model_memory", lambda *models: cleaned.extend(models))
monkeypatch.setattr(speech, "_seed_voxcpm_generation", lambda: calls.setdefault("seeded", True))
result = speech.generate_voxcpm2_speech(
text="字幕123",
prompt="四川话",
cfg=2.3,
steps=12,
reference_audio_path=reference,
)
assert calls["model_path"] == str(model_dir)
assert calls["model_kwargs"] == {
"device": "cpu",
"optimize": False,
}
assert calls["generate"] == {
"text": "(四川话)字幕123",
"cfg_value": 2.3,
"inference_timesteps": 12,
"normalize": True,
"reference_wav_path": str(reference),
}
assert calls["seeded"] is True
assert len(cleaned) == 1
assert isinstance(cleaned[0], FakeVoxCPM)
assert result.source_type == "output"
assert result.file_path.startswith("easy_media/字幕123_")
assert Path(result.absolute_path).is_file()
def test_generate_voxcpm2_speech_accepts_raw_waveform_result(tmp_path, monkeypatch):
model_dir = tmp_path / "models" / "voxcpm" / "VoxCPM2"
model_dir.mkdir(parents=True)
output_dir = tmp_path / "output"
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path / "models"))
monkeypatch.setattr(speech.folder_paths, "get_output_directory", lambda: str(output_dir))
monkeypatch.setattr(speech, "missing_speech_dependencies", lambda: [])
_install_fake_torch(monkeypatch)
calls = {}
class FakeTtsModel:
sample_rate = 24000
class FakeVoxCPM:
tts_model = FakeTtsModel()
@classmethod
def from_pretrained(cls, path, **kwargs):
return cls()
def generate(self, **kwargs):
return [0.0, 0.1, 0.2]
def write_audio_file(path, audio, sample_rate):
calls["audio"] = audio
calls["sample_rate"] = sample_rate
path.write_bytes(b"wav")
voxcpm_module = types.ModuleType("voxcpm")
voxcpm_module.VoxCPM = FakeVoxCPM
monkeypatch.setitem(sys.modules, "voxcpm", voxcpm_module)
monkeypatch.setattr(speech, "_write_audio_file", write_audio_file)
monkeypatch.setattr(speech, "cleanup_model_memory", lambda *models: None)
monkeypatch.setattr(speech, "_seed_voxcpm_generation", lambda: 123)
result = speech.generate_voxcpm2_speech(
text="字幕",
prompt="",
cfg=2.0,
steps=10,
)
assert calls["audio"] == [0.0, 0.1, 0.2]
assert calls["sample_rate"] == 24000
assert result.duration == pytest.approx(3 / 24000)
def test_generate_voxcpm2_speech_cleans_model_memory_on_error(tmp_path, monkeypatch):
model_dir = tmp_path / "models" / "voxcpm" / "VoxCPM2"
model_dir.mkdir(parents=True)
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path / "models"))
monkeypatch.setattr(speech, "missing_speech_dependencies", lambda: [])
cleaned = []
class FakeVoxCPM:
@classmethod
def from_pretrained(cls, path, **kwargs):
return cls()
def generate(self, **kwargs):
raise RuntimeError("boom")
voxcpm_module = types.ModuleType("voxcpm")
voxcpm_module.VoxCPM = FakeVoxCPM
monkeypatch.setitem(sys.modules, "voxcpm", voxcpm_module)
monkeypatch.setattr(speech, "cleanup_model_memory", lambda *models: cleaned.extend(models), raising=False)
with pytest.raises(RuntimeError, match="boom"):
speech.generate_voxcpm2_speech(
text="字幕",
prompt="",
cfg=2.0,
steps=10,
)
assert len(cleaned) == 1
assert isinstance(cleaned[0], FakeVoxCPM)
def test_generate_voxcpm2_speech_disables_warmup_without_cuda_or_mps(tmp_path, monkeypatch):
model_dir = tmp_path / "models" / "voxcpm" / "VoxCPM2"
model_dir.mkdir(parents=True)
output_dir = tmp_path / "output"
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path / "models"))
monkeypatch.setattr(speech.folder_paths, "get_output_directory", lambda: str(output_dir))
monkeypatch.setattr(speech, "missing_speech_dependencies", lambda: [])
calls = {}
_install_fake_torch(monkeypatch)
class FakeVoxCPM:
@classmethod
def from_pretrained(cls, path, **kwargs):
calls["model_path"] = path
calls["model_kwargs"] = kwargs
return cls()
def generate(self, **kwargs):
return 16000, [0.0]
voxcpm_module = types.ModuleType("voxcpm")
voxcpm_module.VoxCPM = FakeVoxCPM
monkeypatch.setitem(sys.modules, "voxcpm", voxcpm_module)
monkeypatch.setattr(speech, "_write_audio_file", lambda path, audio, sample_rate: path.write_bytes(b"wav"))
monkeypatch.setattr(speech, "cleanup_model_memory", lambda *models: None, raising=False)
speech.generate_voxcpm2_speech(
text="字幕",
prompt="",
cfg=2.0,
steps=10,
)
assert calls["model_path"] == str(model_dir)
assert calls["model_kwargs"] == {
"device": "cpu",
"optimize": False,
}
def test_generate_voxcpm2_speech_uses_subtitle_text_when_prompt_is_blank(tmp_path, monkeypatch):
model_dir = tmp_path / "models" / "voxcpm" / "VoxCPM2"
model_dir.mkdir(parents=True)
output_dir = tmp_path / "output"
monkeypatch.setattr(speech.folder_paths, "models_dir", str(tmp_path / "models"))
monkeypatch.setattr(speech.folder_paths, "get_output_directory", lambda: str(output_dir))
monkeypatch.setattr(speech, "missing_speech_dependencies", lambda: [])
_install_fake_torch(monkeypatch)
calls = {}
class FakeVoxCPM:
@classmethod
def from_pretrained(cls, path, **kwargs):
return cls()
def generate(self, **kwargs):
calls["generate"] = kwargs
return 16000, [0.0]
voxcpm_module = types.ModuleType("voxcpm")
voxcpm_module.VoxCPM = FakeVoxCPM
monkeypatch.setitem(sys.modules, "voxcpm", voxcpm_module)
monkeypatch.setattr(speech, "_write_audio_file", lambda path, audio, sample_rate: path.write_bytes(b"wav"))
monkeypatch.setattr(speech, "cleanup_model_memory", lambda *models: None, raising=False)
monkeypatch.setattr(speech, "_seed_voxcpm_generation", lambda: 123)
speech.generate_voxcpm2_speech(
text="字幕",
prompt=" ",
cfg=2.0,
steps=10,
)
assert calls["generate"]["text"] == "字幕"
assert "prompt_text" not in calls["generate"]
assert "prompt_wav_path" not in calls["generate"]