File size: 8,245 Bytes
3a34d7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
"""
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)