2026-09-03 00:10:18 +04:00

131 lines
4.9 KiB
Python

"""List and audition the PC voices available to the simulator.
python scripts/voices.py # list what is installed
python scripts/voices.py --demo # speak a sample line in each voice
python scripts/voices.py --try Zira # hear one voice, with .env's rate/volume
Whichever you prefer goes in .env:
MOCK_LOCAL_AUDIO=true
MOCK_VOICE=Zira
IMPORTANT - this is the *simulator's* voice, not the robot's. The real AGIBOT A3
synthesises speech on-board; AgiBot does not publish which engine or timbre it
uses, so no PC voice can be claimed to match it. This is a stand-in for
rehearsing a demo. See docs/AGIBOT_A3_INTEGRATION.md.
"""
from __future__ import annotations
import argparse
import platform
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
def _use_utf8_console() -> None:
for stream in (sys.stdout, sys.stderr):
try:
if stream is not None and hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
_use_utf8_console()
from backend.config.settings import get_settings # noqa: E402
from backend.robot.local_audio import create_local_voice, list_local_voices # noqa: E402
SAMPLE = "Good afternoon, and welcome to our showroom. I am the AGIBOT A3 humanoid robot."
def speak_with(hint, text, rate, volume, pitch=0) -> None:
voice = create_local_voice(voice_hint=hint, rate=rate, volume=volume, pitch=pitch)
if voice is None:
print(" No speech engine available on this PC.")
return
try:
voice.start(text)
while voice.is_speaking():
time.sleep(0.05)
finally:
voice.close()
def main() -> None:
parser = argparse.ArgumentParser(description="List and audition simulator voices.")
parser.add_argument("--demo", action="store_true", help="Speak a sample in every voice.")
parser.add_argument("--try", dest="which", metavar="NAME", help="Speak a sample in one voice.")
parser.add_argument("--text", default=SAMPLE, help="Say this instead of the default sample.")
args = parser.parse_args()
settings = get_settings()
rate, volume = settings.mock.speech_rate, settings.mock.speech_volume
pitch = settings.mock.speech_pitch
if args.which:
print('\n Voice "{0}" (rate {1}, volume {2}, pitch {3})'.format(
args.which, rate, volume, pitch))
print(" " + args.text + "\n")
speak_with(args.which, args.text, rate, volume, pitch)
return
if settings.mock.voice_engine == "gemini":
from backend.robot.gemini_voice import VOICES as GEMINI_VOICES
current = (settings.mock.gemini_voice or "").lower()
print("\n Gemini neural voices (active: MOCK_VOICE_ENGINE=gemini)")
print(" " + "-" * 58)
for name, tone in sorted(GEMINI_VOICES.items()):
mark = "*" if name.lower() == current else " "
note = " <- young + male, closest to the robot's Yunxiao" \
if name in ("Puck", "Fenrir") else ""
print(" {0} {1:<16} {2}{3}".format(mark, name, tone, note))
print("")
print(" * = current GEMINI_VOICE. Change it in .env, then warm your lines:")
print(' python scripts/warm_voice.py "Welcome to our showroom."')
voices = list_local_voices()
print("\n System voices on this PC (used when MOCK_VOICE_ENGINE=system,")
print(" and as the fallback if Gemini fails)")
print(" " + "-" * 58)
if not voices:
if platform.system().lower().startswith("win"):
print(" None found.")
else:
print(" Listing is Windows-only; on macOS the simulator uses `say`,")
print(" on Linux `espeak-ng`.")
return
for description, source in voices:
marker = "*" if settings.mock.voice and settings.mock.voice.lower() in description.lower() else " "
print(" {0} {1:<48} [{2}]".format(marker, description, source))
print("")
print(" * = currently selected by MOCK_VOICE={0}".format(settings.mock.voice or "(unset)"))
print(" 'onecore' voices are Windows' newer, better-sounding set.")
print("")
print(" Hear one: python scripts/voices.py --try Zira")
print(" Hear all: python scripts/voices.py --demo")
print("")
print(" Want more voices (including Mandarin, to match the A3's demo language)?")
print(" Windows Settings > Time & language > Speech > Manage voices > Add voices.")
print("")
if args.demo:
for description, _ in voices:
short = description.replace("Microsoft ", "").split(" - ")[0]
print(" ->", description)
speak_with(description, "{0} speaking. {1}".format(short, args.text),
rate, volume, pitch)
time.sleep(0.4)
print("")
if __name__ == "__main__":
main()