tts_warm: Tegn og Gæt under --budget og --reserve
Tegn og Gæts replikker står nu først i budget-køen i stedet for at blive lavet før budget-tjekket, så forvarmningen ikke bruger af reserven. Uden --budget laves ingen nye, og kommandoen siger det. Fup og Fakta's faste replikker og budget-køens rækkefølge er som før. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ 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. "
|
||||
"Tegn og Gæts faste replikker (inkl. ét sæt pr. tema) laves først.")
|
||||
"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")
|
||||
@@ -39,10 +39,10 @@ class Command(BaseCommand):
|
||||
self.stdout.write("ElevenLabs er ikke sat op (ELEVENLABS_API_KEY/ELEVENLABS_VOICE_ID). Springer over.")
|
||||
return
|
||||
fest = settings.FEST_VOICE_ID or None
|
||||
# (navn, tekster, stemme); Tegn og Gæt først, da de er få. Tegn og Gæt bruger den almindelige speaker.
|
||||
groups = []
|
||||
if only != "fof":
|
||||
groups.append(("faste Tegn og Gæt-replikker", tegn_texts(), None))
|
||||
# (navn, tekster, stemme). Tegn og Gæt (almindelig speaker) laves kun under --budget, først i køen,
|
||||
# så forvarmningen aldrig bruger af reserven; i --dry-run tælles de med her.
|
||||
tegn = tegn_texts() if only != "fof" else []
|
||||
groups = [("faste Tegn og Gæt-replikker", tegn, None)] if tegn and dry_run else []
|
||||
if only != "tegn":
|
||||
static = [line for k, variants in LINES.items() if not k.endswith("_fest")
|
||||
for line in variants if not re.search(r"\{\w+\}", line)]
|
||||
@@ -66,7 +66,10 @@ class Command(BaseCommand):
|
||||
for name, texts, voice in groups:
|
||||
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 or only == "tegn":
|
||||
if budget <= 0:
|
||||
if tegn:
|
||||
missing = sum(1 for t in tegn if not tts.is_cached(t))
|
||||
self.stdout.write(f"Tegn og Gæt: {missing} af {len(tegn)} replikker mangler; de laves kun med --budget.")
|
||||
return
|
||||
left = tts.credits_left()
|
||||
if left is not None:
|
||||
@@ -75,6 +78,24 @@ class Command(BaseCommand):
|
||||
if budget <= 0:
|
||||
return
|
||||
|
||||
queue = [(t, None) for t in tegn] + (self._fof_queue(fest) if only != "tegn" else [])
|
||||
|
||||
spent = made = cached = 0
|
||||
for text, voice in queue:
|
||||
if tts.is_cached(text, voice):
|
||||
cached += 1
|
||||
continue
|
||||
if spent + len(text) > budget:
|
||||
break
|
||||
if tts.synth_sync(text, voice):
|
||||
spent += len(text)
|
||||
made += 1
|
||||
left = sum(1 for t, v in queue if not tts.is_cached(t, v))
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"{made} nye replikker ({spent} tegn); {cached} lå allerede i cachen; {left} mangler endnu."))
|
||||
|
||||
@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
|
||||
# kategori-replikkerne, så begge dele bliver dækket over et par nætter.
|
||||
narrator = Narrator()
|
||||
@@ -92,18 +113,4 @@ class Command(BaseCommand):
|
||||
per_category = [(t, None) for t in _expand_categories(LINES.get("lie_start", []), normal)]
|
||||
per_category += [(t, fest) for t in _expand_categories(LINES.get("lie_start_fest", []), festive)]
|
||||
queue = [t for pair in zip(questions, per_category) for t in pair]
|
||||
queue += questions[len(per_category):] + per_category[len(questions):]
|
||||
|
||||
spent = made = cached = 0
|
||||
for text, voice in queue:
|
||||
if tts.is_cached(text, voice):
|
||||
cached += 1
|
||||
continue
|
||||
if spent + len(text) > budget:
|
||||
break
|
||||
if tts.synth_sync(text, voice):
|
||||
spent += len(text)
|
||||
made += 1
|
||||
left = sum(1 for t, v in queue if not tts.is_cached(t, v))
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f"{made} nye replikker ({spent} tegn); {cached} lå allerede i cachen; {left} mangler endnu."))
|
||||
return queue + questions[len(per_category):] + per_category[len(questions):]
|
||||
|
||||
@@ -130,49 +130,76 @@ class TegnWarmTextTests(SimpleTestCase):
|
||||
self.assertLessEqual({r["say"] for r in TEGN_RULES}, set(plain))
|
||||
|
||||
|
||||
FOF_QUEUE = [("fup og fakta-spørgsmål", None)] # Fup og Fakta's budget-kø (kræver databasen; erstattes her)
|
||||
|
||||
|
||||
@override_settings(ELEVENLABS_API_KEY="nøgle", ELEVENLABS_VOICE_ID="stemme", FEST_VOICE_ID="")
|
||||
class TtsWarmCommandTests(SimpleTestCase):
|
||||
def run_cmd(self, *args):
|
||||
def run_cmd(self, *args, credits=None):
|
||||
"""credits: hvad ElevenLabs siger er tilbage (None = kald må ikke ske)."""
|
||||
calls = []
|
||||
|
||||
def fake_sync(text, voice=None):
|
||||
calls.append((text, voice))
|
||||
return {"url": "/tts/x.mp3", "ms": 10}
|
||||
|
||||
cached = lambda text, voice=None: (text, voice) in calls # noqa: E731
|
||||
out = io.StringIO()
|
||||
with mock.patch.object(tts, "synth_sync", side_effect=fake_sync), \
|
||||
mock.patch.object(tts, "_fetch", side_effect=AssertionError("netværk")), \
|
||||
mock.patch.object(tts, "credits_left", side_effect=AssertionError("netværk")):
|
||||
credits_mock = (mock.patch.object(tts, "credits_left", return_value=credits) if credits is not None
|
||||
else mock.patch.object(tts, "credits_left", side_effect=AssertionError("netværk")))
|
||||
with mock.patch.object(tts, "synth_sync", side_effect=fake_sync), mock.patch.object(tts, "is_cached", side_effect=cached), \
|
||||
mock.patch.object(tts, "_fetch", side_effect=AssertionError("netværk")), credits_mock, \
|
||||
mock.patch("party.management.commands.tts_warm.Command._fof_queue", return_value=FOF_QUEUE):
|
||||
call_command("tts_warm", *args, stdout=out)
|
||||
return calls, out.getvalue()
|
||||
return [t for t, _ in calls], {v for _, v in calls}, out.getvalue()
|
||||
|
||||
def test_tegn_first_then_fup_og_fakta_as_before(self):
|
||||
calls, out = self.run_cmd()
|
||||
tegn = warm_texts()
|
||||
self.assertEqual([t for t, _ in calls], tegn + fof_static())
|
||||
self.assertEqual({v for _, v in calls}, {None})
|
||||
self.assertIn(f"{len(tegn)} af {len(tegn)} faste Tegn og Gæt-replikker ligger i cachen.", out)
|
||||
def test_without_budget_tegn_is_not_made_and_fof_is_as_before(self):
|
||||
calls, voices, out = self.run_cmd()
|
||||
self.assertEqual(calls, fof_static())
|
||||
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)
|
||||
|
||||
def test_budget_makes_tegn_first_then_the_fof_queue(self):
|
||||
calls, voices, out = self.run_cmd("--budget", "100000", credits=10**6)
|
||||
self.assertEqual(calls, fof_static() + 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 = 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)
|
||||
|
||||
def test_under_the_reserve_no_tegn_clips(self):
|
||||
calls, _, out = self.run_cmd("--budget", "100000", credits=40000) # reserve 40000
|
||||
self.assertEqual(calls, fof_static())
|
||||
calls, _, _ = self.run_cmd("--only", "tegn", "--budget", "100000", "--reserve", "5000", credits=4000)
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_only(self):
|
||||
calls, _ = self.run_cmd("--only", "tegn", "--budget", "1000") # Tegn og Gæt har ingen budget-kø
|
||||
self.assertEqual([t for t, _ in calls], warm_texts())
|
||||
calls, _ = self.run_cmd("--only", "fof")
|
||||
self.assertEqual([t for t, _ in calls], fof_static())
|
||||
calls, _, out = self.run_cmd("--only", "tegn")
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn("laves kun med --budget", out)
|
||||
calls, _, _ = self.run_cmd("--only", "tegn", "--budget", "12000", credits=10**6)
|
||||
self.assertEqual(calls, warm_texts()) # 286 klip, 11771 tegn
|
||||
calls, _, out = self.run_cmd("--only", "fof", "--budget", "100000", credits=10**6)
|
||||
self.assertEqual(calls, fof_static() + [t for t, _ in FOF_QUEUE])
|
||||
self.assertNotIn("Tegn og Gæt", out)
|
||||
|
||||
@override_settings(ELEVENLABS_API_KEY="", ELEVENLABS_VOICE_ID="")
|
||||
def test_dry_run_counts_without_key_or_network(self):
|
||||
calls, out = self.run_cmd("--dry-run", "--only", "tegn")
|
||||
calls, _, out = self.run_cmd("--dry-run", "--only", "tegn")
|
||||
self.assertEqual(calls, [])
|
||||
tegn = warm_texts()
|
||||
self.assertIn(f"faste Tegn og Gæt-replikker: {len(tegn)} klip, {sum(map(len, tegn))} tegn", out)
|
||||
calls, out = self.run_cmd("--dry-run", "--budget", "500")
|
||||
calls, _, out = self.run_cmd("--dry-run", "--budget", "500")
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn(f"faste replikker: {len(fof_static())} klip", out)
|
||||
self.assertIn("tælles ikke i --dry-run", out)
|
||||
|
||||
@override_settings(ELEVENLABS_API_KEY="")
|
||||
def test_without_key_and_without_dry_run_it_still_skips(self):
|
||||
calls, out = self.run_cmd()
|
||||
calls, _, out = self.run_cmd()
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn("Springer over", out)
|
||||
|
||||
Reference in New Issue
Block a user