"""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 # Anche senza GPU (collaudo locale): XTTS_DEVICE=cpu, stesso modello e stesse voci. device = os.environ.get("XTTS_DEVICE", "cuda") if device == "cpu": torch.set_num_threads(2) model_dir = os.environ.get("XTTS_MODEL_DIR") if model_dir: # Modello già scaricato: nessun controllo di aggiornamenti in rete. from TTS.utils.synthesizer import Synthesizer tts = TTS() tts.synthesizer = Synthesizer(model_dir=model_dir, use_cuda=device == "cuda") else: tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device) 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")