tts-voice-creator-clone-and.../tests/test_seed_autopick.py
mARTin-B78 b36423457b feat(design): auto-pick the best seed by scored candidates
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 16:37:58 +02:00

112 lines
4.3 KiB
Python

"""Seed auto-pick: scoring, selection and the endpoint with all I/O mocked."""
import asyncio
import os
import tempfile
import io
import sys
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-autopick-test-"))
from fastapi import HTTPException
from pydub.generators import Sine
from core import seed_score as sc
import routes.tts as tts
def wav(gain=-20, gap=False):
seg = Sine(300, sample_rate=24000).to_audio_segment(1500).apply_gain(gain)
if gap:
seg = seg + seg.__class__.silent(2000, 24000) + seg
buf = io.BytesIO()
seg.export(buf, format="wav")
return buf.getvalue()
class FakeReq:
def __init__(self, body, disconnect_after=None):
self.body, self.calls, self.after = body, 0, disconnect_after
async def json(self):
return self.body
async def is_disconnected(self):
self.calls += 1
return self.after is not None and self.calls > self.after
def run(req, gen, stt):
with mock.patch.object(tts, "_seed_finder_request_audio", side_effect=gen), \
mock.patch.object(tts, "_load_settings", return_value={}), \
mock.patch("routes.stt._transcribe_audio", side_effect=stt):
return asyncio.run(tts.seed_autopick(req))
class ScoringTests(unittest.TestCase):
def test_combine_and_degraded(self):
self.assertEqual(sc.combine(100, 100, 100), 100.0)
self.assertEqual(sc.combine(None, 100, 50), 70.0) # (0.2*100+0.3*50)/0.5
self.assertLess(sc.loudness_score({"dbfs": -5, "peak_dbfs": 0, "target_dbfs": -20}), 20)
self.assertEqual(sc.loudness_score({"dbfs": None}), 0.0)
self.assertEqual(sc.problems_score([{"kind": "silence"}] * 5), 0.0)
def test_winner_tiebreak_and_errors(self):
c = [{"seed": 3, "score": 80}, {"seed": 2, "score": 80}, {"seed": 1, "error": "x"}]
self.assertEqual(sc.pick_winner(c), 2)
self.assertIsNone(sc.pick_winner([{"seed": 1, "error": "x"}]))
def test_seed_list_bounds(self):
self.assertEqual(sc.seed_list(None, None), list(range(1, 9)))
self.assertEqual(sc.seed_list([5, 5, 7], None), [5, 7])
for bad in ((None, 17), (None, 0), ([], None), ([-1], None), (list(range(17)), None), (["a"], None)):
with self.assertRaises(ValueError):
sc.seed_list(*bad)
class EndpointTests(unittest.TestCase):
body = {"voice": "DE_x", "text": "hallo welt", "seeds": [1, 2, 3]}
def test_picks_best_and_never_saves(self):
audios = {1: wav(-20, gap=True), 2: wav(-20), 3: wav(-3)}
gen = lambda text, voice, settings, seed: (audios[seed], "audio/wav")
stt = lambda *a, **k: ("hallo welt", "x")
out = run(FakeReq(self.body), gen, stt)
self.assertEqual(out["winner"], 2)
self.assertFalse(out["degraded"])
self.assertEqual([c["seed"] for c in out["candidates"]], [1, 2, 3])
self.assertTrue(all("audio_b64" in c and "problems" in c for c in out["candidates"]))
self.assertGreater(out["candidates"][1]["score"], out["candidates"][0]["score"])
def test_stt_down_degrades(self):
def stt(*a, **k):
raise RuntimeError("no stt")
out = run(FakeReq(self.body), lambda *a: (wav(), "audio/wav"), stt)
self.assertTrue(out["degraded"])
self.assertIn("no stt", out["stt"]["note"])
self.assertIsNotNone(out["winner"])
self.assertIsNone(out["candidates"][0]["accuracy"])
def test_error_candidate_and_cancel(self):
def gen(text, voice, settings, seed):
if seed == 1:
raise RuntimeError("boom")
return wav(), "audio/wav"
out = run(FakeReq(self.body, disconnect_after=2), gen, lambda *a, **k: ("hallo welt", "x"))
self.assertEqual(out["candidates"][0]["error"], "boom")
self.assertEqual(out["winner"], 2)
self.assertTrue(out["cancelled"])
self.assertEqual(len(out["candidates"]), 2)
def test_invalid_input(self):
for body in ({"voice": "", "text": "a"}, {"voice": "v", "text": " "}, {"voice": "v", "text": "a", "count": 99}):
with self.assertRaises(HTTPException):
run(FakeReq(body), lambda *a: (wav(), "audio/wav"), lambda *a, **k: ("", ""))
if __name__ == "__main__":
unittest.main()