EdgeSanskrit-TTS / generate_from_english.py
Hari7718's picture
Initial commit: Complete offline bundle with IndicF5 and Vocos weights
3a34d7a verified
Raw
History Blame Contribute Delete
8.25 kB
"""
generate_from_english.py β€” EdgeSanskrit English-to-Sanskrit-Audio Pipeline
============================================================================
Pipeline:
1. Take English text (hardcoded or --text argument)
2. Translate to Sanskrit Devanagari via OpenRouter (with model fallback)
3. Feed Sanskrit into IndicF5 CPU engine
4. Save output WAV
"""
import os
import sys
import json
import time
import requests
from dotenv import load_dotenv
# Force UTF-8 console output on Windows
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
load_dotenv()
HERE = os.path.dirname(os.path.abspath(__file__))
os.environ["VAGDHENU_SRC"] = os.path.join(HERE, "vagdhenu", "src")
# ── English text (edit freely) ────────────────────────────────────────────────
ENGLISH_TEXT = """\
Chapter 1: The World That Rejected Miracles
The first thing I heard after dying was someone screaming.
Your Highness! He's breathing!
I opened my eyes. Stone ceiling. Golden chandeliers. The smell of blood.
A young woman in white robes was crying beside the bed while several armored knights
pointed their swords at me.
Impossible. The prince died. So who is he?
I had no answer.
"""
# ─────────────────────────────────────────────────────────────────────────────
# Step 1: Translate via OpenRouter (Gemini Flash β†’ Claude Haiku β†’ Mistral 7B)
# ─────────────────────────────────────────────────────────────────────────────
MODELS = [
("google/gemini-flash-1.5", "Gemini Flash 1.5"),
("anthropic/claude-3-haiku", "Claude 3 Haiku"),
("mistralai/mistral-7b-instruct", "Mistral 7B Instruct"),
]
def translate_to_sanskrit(english: str, context: str = "narrative prose") -> str:
api_key = os.getenv("OPENROUTER_API_KEY", "").strip()
if not api_key:
print("[ERROR] OPENROUTER_API_KEY not found in .env")
sys.exit(1)
prompt = (
"You are a master Sanskrit scholar specializing in classical narrative literature.\n"
f"Translate the following English {context} into elegant Sanskrit Devanagari script.\n"
"Preserve the dramatic tone and narrative flow. Use Anushtubh meter where possible for verse.\n"
"CRITICAL: Your response must be ONLY the pure Devanagari Sanskrit text.\n"
"No English, no transliteration, no explanation, no markdown, no quotes.\n\n"
f"English text:\n{english.strip()}"
)
last_err = None
for model_id, model_name in MODELS:
try:
print(f"[OpenRouter] Trying {model_name}...")
resp = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "http://localhost:8000",
"X-Title": "EdgeSanskrit",
},
json={
"model": model_id,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3,
"max_tokens": 1024,
},
timeout=40,
)
resp.raise_for_status()
raw = resp.json()["choices"][0]["message"]["content"].strip()
# Strip any markdown fencing the model might add
for tag in ["```devanagari", "```sanskrit", "```"]:
raw = raw.replace(tag, "")
sanskrit = raw.strip()
if not sanskrit:
raise ValueError("Empty response from model")
print(f"[OpenRouter] SUCCESS via {model_name}")
return sanskrit
except Exception as e:
print(f"[OpenRouter] {model_name} failed: {e} β€” trying next...")
last_err = e
continue
raise RuntimeError(f"All translation models failed. Last: {last_err}")
# ─────────────────────────────────────────────────────────────────────────────
# Step 2: Synthesize Sanskrit audio via IndicF5
# ─────────────────────────────────────────────────────────────────────────────
def synthesize_audio(sanskrit: str, out_path: str, meter: str = "anushtubh"):
import soundfile as sf
from generate_sanskrit_v2 import SanskritChantEngine
print(f"\n[Engine] Loading IndicF5 on CPU (nfe=12)...")
engine = SanskritChantEngine(voice_model="indicf5", device="cpu", nfe_step=12, speed=0.90)
print(f"[Engine] Synthesizing ({len(sanskrit)} chars)...")
t0 = time.perf_counter()
sr, audio = engine.synthesize(text=sanskrit, meter=meter, seed=42)
elapsed = time.perf_counter() - t0
dur = len(audio) / sr
rtf = elapsed / dur if dur > 0 else 0
sf.write(out_path, audio, sr, subtype="PCM_16")
print(f"[Engine] Done! Duration={dur:.2f}s | Elapsed={elapsed:.1f}s | RTF={rtf:.2f}x")
print(f"[Engine] Saved to: {out_path}")
# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="EdgeSanskrit: English β†’ Sanskrit β†’ Audio")
parser.add_argument("--text", type=str, default=None,
help="English text to synthesize (uses built-in chapter text if omitted)")
parser.add_argument("--meter", type=str, default="anushtubh",
choices=["anushtubh", "vasantatilaka", "malini", "sragdhara", "upajati"])
parser.add_argument("--output", type=str, default="chapter1_sanskrit.wav",
help="Output WAV filename")
parser.add_argument("--translate-only", action="store_true",
help="Only translate, skip audio generation")
args = parser.parse_args()
english = args.text or ENGLISH_TEXT
print("=" * 60)
print(" EdgeSanskrit: English to Sanskrit Audio")
print("=" * 60)
print(f"\n[Input] {len(english)} chars of English text")
print("-" * 60)
# Step 1: Translate
print("\n[Step 1] Translating to Sanskrit via OpenRouter...")
sanskrit = translate_to_sanskrit(english)
print("\n" + "=" * 60)
print(" SANSKRIT TRANSLATION:")
print("=" * 60)
print(sanskrit)
print("=" * 60)
# Cache it
cache_path = os.path.join(HERE, "translations.json")
try:
with open(cache_path, "r", encoding="utf-8") as f:
cache = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
cache = {}
cache[english.strip()[:80] + "..."] = sanskrit
with open(cache_path, "w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False, indent=4)
print(f"\n[Cache] Translation saved to translations.json")
if args.translate_only:
print("\n[Done] Translation complete (--translate-only mode, skipping audio)")
sys.exit(0)
# Step 2: Synthesize
print(f"\n[Step 2] Generating Sanskrit audio...")
out_path = os.path.join(HERE, args.output)
synthesize_audio(sanskrit, out_path, meter=args.meter)
print("\n" + "=" * 60)
print(f" OUTPUT: {out_path}")
print("=" * 60)