Files
leggenda-del-regolo/tools/voci_xtts.py
T
manzoloandClaude Opus 5.5 470d6e7f7b Voci dei personaggi con XTTS: una per battuta, castello e leone con voci proprie
tools/voci.mjs (make voci) genera un mp3 per ogni battuta con la voce del personaggio scelta in
tools/voci.json, solo per i testi nuovi o cambiati; tools/voci_xtts.py divide le frasi e toglie
punti finali e caporali, che XTTS in italiano leggeva («Buongiorno, punto»). Nel gioco il fumetto
dura quanto la voce, la musica si abbassa mentre si parla, il muto spegne anche le voci.
Il leone ora parla e i personaggi parlano dalla posizione che la scena dichiara (speakers),
che prima era ignorata. Tutte le voci verificate con Whisper; test: ogni battuta ha la sua
voce, il fumetto non finisce prima della voce, la musica si abbassa (provati con errori iniettati).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdeKd62opy9PKgpQXtVctF
2026-10-23 15:23:08 +02:00

40 lines
1.7 KiB
Python

"""Sintesi delle battute con XTTS-v2 (gira nel Python di XTTS, lo lancia tools/voci.mjs):
voci_xtts.py lavori.json
lavori.json: [{"text": ..., "speaker": ..., "out": "/assoluto/x.wav"}, ...]; un lavoro già fatto si salta.
In italiano XTTS legge i punti a fine frase («Buongiorno, punto») e inciampa sui caporali: ogni battuta si
divide in frasi, senza punti finali né caporali, e le frasi si uniscono con una pausa breve.
Il modello è sotto licenza Coqui Public Model License: solo uso non commerciale."""
import json
import os
import re
import sys
PAUSA = 0.22 # secondi fra una frase e l'altra
os.environ["COQUI_TOS_AGREED"] = "1"
def frasi(testo: str) -> list[str]:
testo = re.sub(r"[«»\"“”]", "", testo)
parti = re.split(r"(?<=[.!?…])\s+", testo.strip())
pulite = [re.sub(r"(\.\.\.|…|\.)+$", "", p).strip() for p in parti]
return [p for p in pulite if re.search(r"\w", p)]
lavori = [j for j in json.load(open(sys.argv[1])) if not os.path.exists(j["out"])]
if lavori:
import torch
import torchaudio
from TTS.api import TTS
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to("cuda")
sr = tts.synthesizer.output_sample_rate
for i, j in enumerate(lavori, 1):
pezzi = []
for f in frasi(j["text"]):
if pezzi:
pezzi.append(torch.zeros(int(sr * PAUSA)))
pezzi.append(torch.tensor(tts.tts(f, language="it", speaker=j["speaker"], split_sentences=False), dtype=torch.float32))
torchaudio.save(j["out"] + ".tmp.wav", torch.cat(pezzi).unsqueeze(0), sr, format="wav")
os.replace(j["out"] + ".tmp.wav", j["out"])
print(f"{i}/{len(lavori)}", flush=True)
print(f"{len(lavori)} battute sintetizzate")