tts_warm: forvarm de generiske replikker lige efter introen
De generiske replikker (fælles, Fup og Fakta, Tegn og Gæt og afsløringens intro med første kæde) står i budget-køen efter menuens intro og før Tegn og Gæts faste replikker; rækkefølgen bag dem er uændret. Fest- replikkerne laves med fest-stemmen, som rummet siger dem. --only begrænser til det valgte spil, og --dry-run viser dem på deres egen linje: generiske replikker: 310 klip, 16737 tegn (--only tegn: 153 klip, 8685 tegn) Test: alt, et rum kan sige som generisk afløser, ligger i forvarmningen med den stemme, rummet slår op med. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,9 @@ from django.conf import settings
|
||||
from fupogfakta.engine import RULES, RULES_FEST
|
||||
from fupogfakta.models import Category, Question
|
||||
from party import games, tts
|
||||
from party.narrator import LINES, TEASES, TEASES_FEST, Narrator, speakable_prompt
|
||||
from party.tegn import warm_texts as tegn_texts
|
||||
from party.narrator import (GENERIC, GENERIC_COMMON, LINES, TEASES, TEASES_FEST, Narrator, generic_texts,
|
||||
speakable_prompt)
|
||||
from party.tegn import warm_generic as tegn_generic, warm_texts as tegn_texts
|
||||
|
||||
|
||||
def _expand_categories(lines: list[str], categories: list[str]) -> list[str]:
|
||||
@@ -24,8 +25,8 @@ class Command(BaseCommand):
|
||||
help = ("Genererer speakerens replikker på forhånd. Uden --budget: kun de faste replikker (gratis, når de findes). "
|
||||
"Med --budget N: derefter op til N tegn af spørgsmålenes oplæsning og forklaring (de mindst brugte først, "
|
||||
"dvs. dem der kommer næste gang) og kategori-replikkerne. Køres om natten, så kvoten fordeles. "
|
||||
"Menuens intro-replikker og Tegn og Gæts replikker står først i budget-køen (de tæller med i --budget "
|
||||
"og --reserve).")
|
||||
"Menuens intro-replikker, de generiske replikker uden navne og Tegn og Gæts replikker står først i "
|
||||
"budget-køen (de tæller med i --budget og --reserve).")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--budget", type=int, default=0, help="højst så mange nye tegn ud over de faste replikker")
|
||||
@@ -45,6 +46,7 @@ class Command(BaseCommand):
|
||||
tegn = tegn_texts() if only != "fof" else []
|
||||
# menuens intro pr. spil (standardstemmen, som /host/intro/<spil>); med --only kun det spils
|
||||
intros = [g.intro() for g in games.GAMES.values() if only in (None, g.key)]
|
||||
generic = self._generic(only, fest) # [(tekst, stemme)]: det rummene siger, når en replik ikke kan laves
|
||||
first = [("menuens intro-replikker", intros), ("Tegn og Gæt", tegn)]
|
||||
groups = [("menuens intro-replikker", intros, None)] if dry_run else []
|
||||
groups += [("faste Tegn og Gæt-replikker", tegn, None)] if tegn and dry_run else []
|
||||
@@ -61,8 +63,12 @@ class Command(BaseCommand):
|
||||
fest_static += TEASES_FEST + [r["say"] for r in RULES_FEST]
|
||||
groups.append(("faste fest-replikker", fest_static, fest))
|
||||
if dry_run:
|
||||
for name, texts, voice in groups:
|
||||
missing = [t for t in texts if not tts.is_cached(t, voice)]
|
||||
# i budget-køens rækkefølge: intro, generiske (hver med sin stemme), Tegn og Gæt, Fup og Fakta's faste
|
||||
rows = [(name, [(t, voice) for t in texts]) for name, texts, voice in groups]
|
||||
rows.insert(1, ("generiske replikker", generic))
|
||||
for name, pairs in rows:
|
||||
texts = [t for t, _ in pairs]
|
||||
missing = [t for t, v in pairs if not tts.is_cached(t, v)]
|
||||
self.stdout.write(f"{name}: {len(texts)} klip, {sum(map(len, texts))} tegn; "
|
||||
f"mangler i cachen: {len(missing)} klip, {sum(map(len, missing))} tegn.")
|
||||
if budget > 0 and only != "tegn":
|
||||
@@ -72,6 +78,8 @@ class Command(BaseCommand):
|
||||
ok = sum(1 for line in texts if tts.synth_sync(line, voice))
|
||||
self.stdout.write(self.style.SUCCESS(f"{ok} af {len(texts)} {name} ligger i cachen."))
|
||||
if budget <= 0:
|
||||
missing = sum(1 for t, v in generic if not tts.is_cached(t, v))
|
||||
self.stdout.write(f"Generiske replikker: {missing} af {len(generic)} mangler; de laves kun med --budget.")
|
||||
for name, texts in first:
|
||||
if texts:
|
||||
missing = sum(1 for t in texts if not tts.is_cached(t))
|
||||
@@ -85,7 +93,8 @@ class Command(BaseCommand):
|
||||
if budget <= 0:
|
||||
return
|
||||
|
||||
queue = [(t, None) for t in intros + tegn] + (self._fof_queue(fest) if only != "tegn" else [])
|
||||
queue = [(t, None) for t in intros] + generic + [(t, None) for t in tegn]
|
||||
queue += self._fof_queue(fest) if only != "tegn" else []
|
||||
|
||||
spent = made = cached = 0
|
||||
for text, voice in queue:
|
||||
@@ -101,6 +110,18 @@ class Command(BaseCommand):
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"{made} nye replikker ({spent} tegn); {cached} lå allerede i cachen; {left} mangler endnu."))
|
||||
|
||||
@staticmethod
|
||||
def _generic(only, fest) -> list[tuple[str, str | None]]:
|
||||
"""De generiske replikker for de valgte spil; de fælles (velkomst, antal) bruges af begge."""
|
||||
out = generic_texts(GENERIC_COMMON, fest)
|
||||
if only == "tegn": # fest findes kun i Fup og Fakta
|
||||
out = [(t, v) for t, v in out if v is None]
|
||||
if only != "tegn":
|
||||
out += generic_texts(GENERIC, fest)
|
||||
if only != "fof":
|
||||
out += [(t, None) for t in tegn_generic()]
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
@staticmethod
|
||||
def _fof_queue(fest) -> list[tuple[str, str | None]]:
|
||||
# Spørgsmålene i den rækkefølge, spillet vælger dem (mindst brugte først), skiftevis med
|
||||
|
||||
@@ -114,6 +114,13 @@ def with_generic(text: str | None, generic) -> Line | None:
|
||||
return line
|
||||
|
||||
|
||||
def generic_texts(generic: dict, fest_voice: str | None) -> list[tuple[str, str | None]]:
|
||||
"""Alle generiske replikker i en generic.json som (tekst, stemme), som rummet siger dem: fest-replikkerne
|
||||
med fest-stemmen (eller standardstemmen, hvis der ikke er en), resten med standardstemmen."""
|
||||
return [(t, fest_voice if k.endswith("_fest") else None)
|
||||
for k, v in generic.items() if isinstance(v, list) for t in v]
|
||||
|
||||
|
||||
def instead_of(text: str | None, fixed: Line | None) -> Line | None:
|
||||
"""Hjernens replik i stedet for en fast: kan den ikke laves, siges den faste (hvis den er cachet), ellers den
|
||||
faste replik's generiske udgave. Hjernens tekst caches aldrig, så uden live-stemme er det altid afløseren."""
|
||||
|
||||
+9
-1
@@ -19,7 +19,7 @@ from tegnoggaet.engine import EMPTY_GUESS, MAX_PLAYERS, MIN_PLAYERS, RULES, RULE
|
||||
from tegnoggaet.themes import load_themes
|
||||
|
||||
from . import rooms, tts
|
||||
from .narrator import Narrator, one_of, untag
|
||||
from .narrator import Narrator, generic_texts, one_of, untag
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -275,6 +275,14 @@ def warm_texts(themes: list[dict] | None = None) -> list[str]:
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
|
||||
def warm_generic() -> list[str]:
|
||||
"""Tegn og Gæts generiske replikker (standardstemmen), også afsløringens intro med første kædes."""
|
||||
n = Narrator(lines=LINES, generic=GENERIC)
|
||||
out = [t for t, _ in generic_texts(GENERIC, None)]
|
||||
out += [t for ri in n.variants("reveal_intro") for t in first_chain_generics(n, ri)]
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
|
||||
def first_chain_generics(narrator: Narrator, reveal_intro: str) -> list[str]:
|
||||
"""Afsløringens intro efterfulgt af en generisk intro til første kæde, som rummet siger dem."""
|
||||
return [f"{reveal_intro} {v}" for v in narrator.generic_variants("chain_intro_first")]
|
||||
|
||||
@@ -134,6 +134,12 @@ FOF_QUEUE = [("fup og fakta-spørgsmål", None)] # Fup og Fakta's budget-kø (k
|
||||
INTROS = [g.intro() for g in games.GAMES.values()] # menuens intro, én pr. spil
|
||||
|
||||
|
||||
def generic(only=None):
|
||||
"""De generiske replikker, som tts_warm forvarmer dem (FEST_VOICE_ID er tom her, så alt er standardstemmen)."""
|
||||
from party.management.commands.tts_warm import Command
|
||||
return [t for t, _ in Command._generic(only, None)]
|
||||
|
||||
|
||||
@override_settings(ELEVENLABS_API_KEY="nøgle", ELEVENLABS_VOICE_ID="stemme", FEST_VOICE_ID="")
|
||||
class TtsWarmCommandTests(SimpleTestCase):
|
||||
def run_cmd(self, *args, credits=None):
|
||||
@@ -160,15 +166,16 @@ class TtsWarmCommandTests(SimpleTestCase):
|
||||
self.assertIn(f"{len(fof_static())} af {len(fof_static())} faste replikker ligger i cachen.", out)
|
||||
self.assertIn(f"Tegn og Gæt: {len(warm_texts())} af {len(warm_texts())} replikker mangler; de laves kun med --budget.", out)
|
||||
self.assertIn(f"Menuens intro-replikker: {len(INTROS)} af {len(INTROS)} replikker mangler", out)
|
||||
self.assertIn(f"Generiske replikker: {len(generic())} af {len(generic())} mangler", out)
|
||||
|
||||
def test_budget_makes_intros_and_tegn_first_then_the_fof_queue(self):
|
||||
calls, voices, out = self.run_cmd("--budget", "100000", credits=10**6)
|
||||
self.assertEqual(calls, fof_static() + INTROS + warm_texts() + [t for t, _ in FOF_QUEUE])
|
||||
self.assertEqual(calls, fof_static() + INTROS + generic() + warm_texts() + [t for t, _ in FOF_QUEUE])
|
||||
self.assertEqual(voices, {None}) # samme stemme som rummet
|
||||
|
||||
def test_budget_stops_tegn_at_the_budget(self):
|
||||
calls, _, out = self.run_cmd("--only", "tegn", "--budget", "300", credits=10**6)
|
||||
tegn = [games.GAMES["tegn"].intro()] + warm_texts()
|
||||
tegn = [games.GAMES["tegn"].intro()] + generic("tegn") + warm_texts()
|
||||
self.assertEqual(calls, tegn[:len(calls)]) # i rækkefølge, fra starten
|
||||
self.assertLessEqual(sum(map(len, calls)), 300)
|
||||
self.assertGreater(sum(map(len, tegn[:len(calls) + 1])), 300)
|
||||
@@ -183,10 +190,10 @@ class TtsWarmCommandTests(SimpleTestCase):
|
||||
calls, _, out = self.run_cmd("--only", "tegn")
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn("laves kun med --budget", out)
|
||||
calls, _, _ = self.run_cmd("--only", "tegn", "--budget", "13000", credits=10**6)
|
||||
self.assertEqual(calls, [games.GAMES["tegn"].intro()] + warm_texts()) # 287 klip
|
||||
calls, _, _ = self.run_cmd("--only", "tegn", "--budget", "25000", credits=10**6)
|
||||
self.assertEqual(calls, [games.GAMES["tegn"].intro()] + generic("tegn") + warm_texts())
|
||||
calls, _, out = self.run_cmd("--only", "fof", "--budget", "100000", credits=10**6)
|
||||
self.assertEqual(calls, fof_static() + [games.GAMES["fof"].intro()] + [t for t, _ in FOF_QUEUE])
|
||||
self.assertEqual(calls, fof_static() + [games.GAMES["fof"].intro()] + generic("fof") + [t for t, _ in FOF_QUEUE])
|
||||
self.assertNotIn("Tegn og Gæt", out)
|
||||
|
||||
@override_settings(ELEVENLABS_API_KEY="", ELEVENLABS_VOICE_ID="")
|
||||
@@ -197,6 +204,8 @@ class TtsWarmCommandTests(SimpleTestCase):
|
||||
self.assertIn(f"faste Tegn og Gæt-replikker: {len(tegn)} klip, {sum(map(len, tegn))} tegn", out)
|
||||
tegn_intro = games.GAMES["tegn"].intro()
|
||||
self.assertIn(f"menuens intro-replikker: 1 klip, {len(tegn_intro)} tegn", out)
|
||||
g = generic("tegn")
|
||||
self.assertIn(f"generiske replikker: {len(g)} klip, {sum(map(len, g))} tegn", out)
|
||||
calls, _, out = self.run_cmd("--dry-run", "--budget", "500")
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn(f"faste replikker: {len(fof_static())} klip", out)
|
||||
@@ -207,3 +216,23 @@ class TtsWarmCommandTests(SimpleTestCase):
|
||||
calls, _, out = self.run_cmd()
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn("Springer over", out)
|
||||
|
||||
|
||||
class GenericWarmTests(SimpleTestCase):
|
||||
def test_every_generic_a_room_can_say_is_warmed_with_its_voice(self):
|
||||
"""Samme tekst og stemme som BaseRoom._synth slår op med (rummets stemme: fest-stemmen i fest-spil)."""
|
||||
from party.management.commands.tts_warm import Command
|
||||
from party import narrator, tegn
|
||||
warm = set(Command._generic(None, "fest"))
|
||||
for lines, gen, counts in ((narrator.LINES, narrator.GENERIC, range(2, 9)),
|
||||
(tegn.LINES, tegn.GENERIC, range(3, 11))):
|
||||
for fest, voice in ((False, None), (True, "fest")) if lines is narrator.LINES else ((False, None),):
|
||||
n = Narrator(lines=lines, generic=gen, fest=fest)
|
||||
for event in lines:
|
||||
for k in counts:
|
||||
for t in n.generic_variants(event.removesuffix("_fest"), antal_spillere=k):
|
||||
self.assertIn((t, voice), warm, (event, fest))
|
||||
n = Narrator(lines=tegn.LINES, generic=tegn.GENERIC)
|
||||
for ri in n.variants("reveal_intro"):
|
||||
for t in tegn.first_chain_generics(n, ri):
|
||||
self.assertIn((t, None), warm)
|
||||
|
||||
Reference in New Issue
Block a user