112 lines
4.3 KiB
Python
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()
|