Luna-1.0-Audio / luna_cli.py
openmodelai's picture
Upload 44 files
b83f555 verified
Raw History Blame Contribute Delete
8.34 kB
#!/usr/bin/env python3
"""Luna 1.0 CLI — Двуязычный чат (RU/EN), озвучка текста и генерация звуков.
Примеры запуска (внутри распакованной папки luna-1.0):
# 1. Спросить на русском и получить текстовый ответ + WAV-озвучку
python luna_cli.py "Привет, Луна! Расскажи коротко, что ты умеешь." --out reply_ru.wav
# 2. Спросить на английском с космическим эмбиент-фоном
python luna_cli.py "Hello Luna! Tell me a fun fact about the Moon." --voice luna_ambient --out reply_en.wav
# 3. Озвучить готовый двуязычный текст без генерации ответа (режим TTS)
python luna_cli.py --tts "Привет! Меня зовут Luna 1.0. Nice to meet you!" --out bilingual.wav
# 4. Сгенерировать звуковой эффект или эмбиент
python luna_cli.py --sfx lunar_pad --duration 4.0 --out lunar_pad.wav
# 5. Интерактивный голосовой чат в терминале
python luna_cli.py --interactive
"""
from __future__ import annotations
import argparse
import os
from pathlib import Path
from transformers import AutoModel
DEFAULT_DIR = Path(__file__).resolve().parent
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("prompt", nargs="?", default=None, help="Сообщение для чата или текст для озвучки")
parser.add_argument("--model", default=str(DEFAULT_DIR), help="Путь к папке luna-1.0 или HF repo_id")
parser.add_argument("--out", default="luna_output.wav", help="Куда сохранить WAV-файл (пусто — без записи)")
parser.add_argument("--voice", default="luna_warm", help="Пресет голоса (см. --list-voices)")
parser.add_argument("--lang", default="auto", choices=["auto", "ru", "en"], help="Язык (auto / ru / en)")
parser.add_argument("--tts", action="store_true", help="Озвучить переданный текст напрямую (без LLM-ответа)")
parser.add_argument("--text-only", action="store_true", help="Только текстовый ответ без генерации аудио")
parser.add_argument("--sfx", default=None, help="Сгенерировать звуковой эффект (chime, lunar_pad, success, ...)")
parser.add_argument("--duration", type=float, default=2.5, help="Длительность эффекта --sfx в секундах")
parser.add_argument("--chime", action="store_true", help="Добавить приветственный аккорд Luna перед речью")
parser.add_argument("--ambient", default=None, choices=[None, "lunar", "warm", "focus"], help="Фоновый эмбиент")
parser.add_argument("--speed", type=float, default=None, help="Скорость речи (например 1.0)")
parser.add_argument("--sample-rate", type=int, default=None, help="Частота дискретизации WAV (например 24000)")
parser.add_argument("--max-new-tokens", type=int, default=140, help="Максимум новых токенов ответа")
parser.add_argument("--temperature", type=float, default=0.7, help="Температура генерации")
parser.add_argument("--interactive", action="store_true", help="Интерактивный диалог с записью каждого ответа в WAV")
parser.add_argument("--list-voices", action="store_true", help="Показать доступные голосовые пресеты и выйти")
return parser.parse_args()
def main() -> int:
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
args = parse_args()
luna = AutoModel.from_pretrained(args.model, trust_remote_code=True)
if args.list_voices:
print("Доступные пресеты голосов Luna 1.0:")
for name, cfg in luna.list_voices().items():
desc = cfg.get("description_ru") or cfg.get("description_en") or ""
print(f" - {name:18s} rate={cfg.get('speaking_rate', 1.0)} {desc}")
return 0
if args.sfx:
out_path = args.out or f"{args.sfx}.wav"
res = luna.create_audio_fx(
effect=args.sfx,
duration_sec=args.duration,
wav_path=out_path,
sample_rate=args.sample_rate,
)
print(f"[Luna 1.0 SFX] {res.wav_path} ({res.duration_sec:.2f}s, {res.sample_rate} Hz)")
return 0
if args.interactive:
out_dir = Path("outputs")
out_dir.mkdir(exist_ok=True)
history: list[dict[str, str]] = []
turn = 1
print("=== Luna 1.0 Interactive Bilingual Chat + Audio (RU / EN) ===")
print("Введите сообщение (или 'exit' / 'выход' для завершения).\n")
while True:
try:
user_msg = input("Вы / You > ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not user_msg or user_msg.lower() in {"exit", "quit", "выход", ":q"}:
break
wav_file = out_dir / f"turn_{turn:02d}.wav"
if args.text_only:
reply = luna.chat(
user_msg,
history=history,
language=args.lang,
max_new_tokens=args.max_new_tokens,
temperature=args.temperature,
)
print(f"Luna 1.0 > {reply}\n")
else:
res = luna.chat_and_speak(
user_msg,
history=history,
language=args.lang,
voice=args.voice,
wav_path=wav_file,
chime=True if args.chime else None,
ambient=args.ambient,
speaking_rate=args.speed,
target_sample_rate=args.sample_rate,
max_new_tokens=args.max_new_tokens,
temperature=args.temperature,
)
reply = res.text
print(f"Luna 1.0 > {reply}")
print(f" [audio] {res.wav_path} ({res.duration_sec:.2f}s, lang={res.language}, voice={res.voice})\n")
history.append({"role": "user", "content": user_msg})
history.append({"role": "assistant", "content": reply})
turn += 1
return 0
prompt = args.prompt or "Привет! Представься коротко на русском и английском."
out_wav = args.out if args.out else None
if args.tts:
res = luna.speak(
prompt,
language=args.lang,
voice=args.voice,
wav_path=out_wav,
chime=True if args.chime else None,
ambient=args.ambient,
speaking_rate=args.speed,
target_sample_rate=args.sample_rate,
)
print(f"[Luna 1.0 TTS] {res.text}")
print(f"[Audio] {res.wav_path} ({res.duration_sec:.2f}s, {res.sample_rate} Hz, lang={res.language})")
return 0
if args.text_only:
reply = luna.chat(
prompt,
language=args.lang,
max_new_tokens=args.max_new_tokens,
temperature=args.temperature,
)
print(reply)
return 0
res = luna.chat_and_speak(
prompt,
language=args.lang,
voice=args.voice,
wav_path=out_wav,
chime=True if args.chime else None,
ambient=args.ambient,
speaking_rate=args.speed,
target_sample_rate=args.sample_rate,
max_new_tokens=args.max_new_tokens,
temperature=args.temperature,
)
print(f"Luna 1.0: {res.text}")
if res.wav_path:
print(f"Аудио сохранено: {res.wav_path} ({res.duration_sec:.2f} с, {res.sample_rate} Гц, язык: {res.language})")
return 0
if __name__ == "__main__":
raise SystemExit(main())