Flere stemmer: nvcc (10 norske talere), flere engelske, svensk og dansk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1c3e17afa0
commit
ff43624969
3 changed files with 52 additions and 19 deletions
51
announcer.py
51
announcer.py
|
|
@ -32,14 +32,36 @@ PIN = os.environ.get("ANNOUNCER_PIN", "").strip()
|
|||
SINK = os.environ.get("ANNOUNCER_SINK", "").strip() # pw-play --target
|
||||
MAX_CHARS = 600
|
||||
|
||||
# Stemmene som tilbys i menyen. Lastes ned fra rhasspy/piper-voices ved
|
||||
# første oppstart om de mangler i VOICE_DIR.
|
||||
VOICES: dict[str, str] = {
|
||||
"no_NO-talesyntese-medium": "Norsk (talesyntese)",
|
||||
"en_US-lessac-medium": "English US (Lessac)",
|
||||
"en_GB-alan-medium": "English UK (Alan)",
|
||||
}
|
||||
DEFAULT_VOICE = "no_NO-talesyntese-medium"
|
||||
# Stemmene som tilbys i menyen. Modellene lastes ned fra rhasspy/piper-voices
|
||||
# ved første oppstart om de mangler i VOICE_DIR. «speaker» er bare for
|
||||
# flerstemmemodeller (nvcc har ti norske talere i én modell).
|
||||
def _v(id: str, name: str, group: str, model: str, speaker: int | None = None) -> dict:
|
||||
return {"id": id, "name": name, "group": group, "model": model, "speaker": speaker}
|
||||
|
||||
|
||||
VOICES_LIST: list[dict] = [
|
||||
_v("no-tale", "Talesyntese (nøytral)", "Norsk", "no_NO-talesyntese-medium"),
|
||||
_v("no-knn", "Kvinne – KNN", "Norsk", "no_NO-nvcc-medium", 0),
|
||||
_v("no-ksv", "Kvinne – KSV", "Norsk", "no_NO-nvcc-medium", 1),
|
||||
_v("no-kon", "Kvinne – KON", "Norsk", "no_NO-nvcc-medium", 3),
|
||||
_v("no-kmn", "Kvinne – KMN", "Norsk", "no_NO-nvcc-medium", 8),
|
||||
_v("no-knv", "Kvinne – KNV", "Norsk", "no_NO-nvcc-medium", 9),
|
||||
_v("no-mmn", "Mann – MMN", "Norsk", "no_NO-nvcc-medium", 2),
|
||||
_v("no-mnn", "Mann – MNN", "Norsk", "no_NO-nvcc-medium", 4),
|
||||
_v("no-msv", "Mann – MSV", "Norsk", "no_NO-nvcc-medium", 5),
|
||||
_v("no-mon", "Mann – MON", "Norsk", "no_NO-nvcc-medium", 6),
|
||||
_v("no-mnv", "Mann – MNV", "Norsk", "no_NO-nvcc-medium", 7),
|
||||
_v("en-ryan", "Ryan (US, mann)", "English", "en_US-ryan-high"),
|
||||
_v("en-amy", "Amy (US, kvinne)", "English", "en_US-amy-medium"),
|
||||
_v("en-lessac", "Lessac (US, nøytral)", "English", "en_US-lessac-medium"),
|
||||
_v("en-alan", "Alan (UK, mann)", "English", "en_GB-alan-medium"),
|
||||
_v("en-north", "Northern English (UK, mann)", "English", "en_GB-northern_english_male-medium"),
|
||||
_v("en-jenny", "Jenny (UK, kvinne)", "English", "en_GB-jenny_dioco-medium"),
|
||||
_v("sv-nst", "Svensk (NST)", "Nabospråk", "sv_SE-nst-medium"),
|
||||
_v("da-tale", "Dansk (talesyntese)", "Nabospråk", "da_DK-talesyntese-medium"),
|
||||
]
|
||||
VOICES: dict[str, dict] = {v["id"]: v for v in VOICES_LIST}
|
||||
DEFAULT_VOICE = "no-tale"
|
||||
|
||||
log = logging.getLogger("announcer")
|
||||
app = Flask(__name__, static_folder=None)
|
||||
|
|
@ -57,7 +79,7 @@ def ensure_voices() -> None:
|
|||
import piper.download_voices as dl
|
||||
|
||||
VOICE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
for name in VOICES:
|
||||
for name in sorted({v["model"] for v in VOICES_LIST}):
|
||||
if not (VOICE_DIR / f"{name}.onnx").exists():
|
||||
log.info("laster ned stemme %s", name)
|
||||
dl.download_voice(name, VOICE_DIR)
|
||||
|
|
@ -103,9 +125,10 @@ def synthesize(text: str, voice_name: str, speed: float, chime: bool) -> Path:
|
|||
if path.exists():
|
||||
return path
|
||||
|
||||
voice = load_voice(voice_name)
|
||||
spec = VOICES[voice_name]
|
||||
voice = load_voice(spec["model"])
|
||||
rate = voice.config.sample_rate
|
||||
cfg = SynthesisConfig(length_scale=1.0 / speed)
|
||||
cfg = SynthesisConfig(length_scale=1.0 / speed, speaker_id=spec["speaker"])
|
||||
|
||||
pcm = bytearray()
|
||||
if chime:
|
||||
|
|
@ -145,7 +168,7 @@ class Item:
|
|||
return {
|
||||
"id": self.id,
|
||||
"text": self.text,
|
||||
"voice": VOICES.get(self.voice, self.voice),
|
||||
"voice": VOICES[self.voice]["name"] if self.voice in VOICES else self.voice,
|
||||
"sender": self.sender,
|
||||
"created": self.created,
|
||||
"status": self.status,
|
||||
|
|
@ -270,7 +293,7 @@ def index() -> Response:
|
|||
def config() -> Response:
|
||||
return jsonify(
|
||||
{
|
||||
"voices": [{"id": k, "name": v} for k, v in VOICES.items()],
|
||||
"voices": [{"id": v["id"], "name": v["name"], "group": v["group"]} for v in VOICES_LIST],
|
||||
"default_voice": DEFAULT_VOICE,
|
||||
"max_chars": MAX_CHARS,
|
||||
"pin_required": bool(PIN),
|
||||
|
|
@ -333,7 +356,7 @@ def main() -> None:
|
|||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logging.getLogger("werkzeug").setLevel(logging.WARNING)
|
||||
ensure_voices()
|
||||
load_voice(DEFAULT_VOICE) # varm opp
|
||||
load_voice(VOICES[DEFAULT_VOICE]["model"]) # varm opp
|
||||
log.info("klar på http://%s:%d (PIN: %s, sink: %s)", args.host, args.port, "på" if PIN else "av", SINK or "standard")
|
||||
app.run(host=args.host, port=args.port, threaded=True)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue