firered-tts3 / tests /test_app_audio_loading.py
LPDoctor's picture
fix: load M4A prompts for voice cloning
df66be8
Raw History Blame Contribute Delete
2.41 kB
import ast
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
import numpy as np
import soundfile as sf
import torch
from fireredtts3 import reference_asr
APP_PATH = Path(__file__).parents[1] / "app.py"
class _FakeGradio:
class Error(Exception):
pass
@staticmethod
def Info(_message):
return None
def _load_app_audio_function():
tree = ast.parse(APP_PATH.read_text(encoding="utf-8"))
function = next(
node
for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name == "_load_audio"
)
namespace = {
"gr": _FakeGradio,
"np": np,
"sf": sf,
"torch": torch,
"decode_audio_with_ffmpeg": reference_asr.decode_audio_with_ffmpeg,
}
compiled = compile(ast.Module(body=[function], type_ignores=[]), APP_PATH, "exec")
exec(compiled, namespace) # noqa: S102 - executes trusted local application code
return namespace["_load_audio"]
class AppAudioLoadingIntegrationTests(unittest.TestCase):
@unittest.skipUnless(shutil.which("ffmpeg"), "FFmpeg is required for M4A coverage")
def test_generate_speech_loads_an_m4a_reference_audio(self):
load_audio = _load_app_audio_function()
with tempfile.TemporaryDirectory() as tmpdir:
wav_path = Path(tmpdir) / "reference.wav"
m4a_path = Path(tmpdir) / "reference clip.m4a"
time = np.arange(8000, dtype=np.float32) / 8000
sf.write(wav_path, 0.25 * np.sin(2 * np.pi * 440 * time), 8000)
subprocess.run(
[
"ffmpeg",
"-nostdin",
"-loglevel",
"error",
"-y",
"-i",
str(wav_path),
"-c:a",
"aac",
str(m4a_path),
],
check=True,
)
waveform, sampling_rate = load_audio(m4a_path, max_seconds=20.0)
self.assertEqual(sampling_rate, 16000)
self.assertEqual(waveform.ndim, 2)
self.assertEqual(waveform.shape[0], 1)
self.assertGreaterEqual(waveform.shape[1], 16000)
self.assertLess(waveform.shape[1], 17000)
self.assertLessEqual(float(waveform.abs().max()), 0.95)
if __name__ == "__main__":
unittest.main()