from __future__ import annotations import argparse import json import sys from pathlib import Path BACKEND_ROOT = Path(__file__).resolve().parents[1] if str(BACKEND_ROOT) not in sys.path: sys.path.insert(0, str(BACKEND_ROOT)) if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") from app.schemas.video import GenerateAudioRequest # noqa: E402 from app.services.tts_provider import generate_audio_for_scene_plan # noqa: E402 SAMPLE_PLANS = { "english_soft": { "video_id": "sample-rest-api-en", "scene_plan": { "title": "REST API Explained", "duration_minutes": 1, "language": "English", "style": "clean_explainer", "scenes": [ { "scene_id": 1, "type": "title", "duration_seconds": 7, "screen_text": "REST API Explained", "voice_text": "REST API becomes simple when you remember request, server, and response.", "keywords": ["request", "server", "response"], "visual_hint": "clean title card", }, { "scene_id": 2, "type": "meaning", "duration_seconds": 10, "screen_text": "API = Messenger", "voice_text": "An API is a messenger between frontend and backend. It carries a request and brings back a response.", "keywords": ["frontend", "backend", "response"], "visual_hint": "middleman diagram", }, { "scene_id": 3, "type": "example", "duration_seconds": 10, "screen_text": "Website asks server", "voice_text": "Imagine a website asking the server for student marks. The API safely carries that request.", "keywords": ["website", "server", "request"], "visual_hint": "browser to server arrows", }, { "scene_id": 4, "type": "exam_answer", "duration_seconds": 12, "screen_text": "Write this in exam", "voice_text": "REST API is a standard way for applications to communicate using HTTP methods like GET, POST, PUT, and DELETE.", "keywords": ["HTTP", "GET", "POST", "DELETE"], "visual_hint": "answer card", }, { "scene_id": 5, "type": "recap", "duration_seconds": 8, "screen_text": "Request to response", "voice_text": "Remember the chain. Frontend sends request. API talks to backend. Backend sends response.", "keywords": ["frontend", "API", "backend"], "visual_hint": "three-step recap", }, ], }, }, "ml_en_mix": { "video_id": "sample-rest-api-mix", "scene_plan": { "title": "REST API Simple Explanation", "duration_minutes": 1, "language": "Malayalam+English", "style": "clean_explainer", "scenes": [ { "scene_id": 1, "type": "hook", "duration_seconds": 8, "screen_text": "REST API simple ayi", "voice_text": "ഇപ്പോൾ നമുക്ക് REST API simple ആയി മനസ്സിലാക്കാം.", "segments": [ {"lang": "ml", "text": "ഇപ്പോൾ നമുക്ക്"}, {"lang": "en", "text": "REST API"}, {"lang": "ml", "text": "simple ആയി മനസ്സിലാക്കാം."}, ], "keywords": ["REST API", "simple"], "visual_hint": "warm hook card", }, { "scene_id": 2, "type": "meaning", "duration_seconds": 10, "screen_text": "Website to database?", "voice_text": "Imagine ചെയ്യൂ, ഒരു website direct database-നോട് സംസാരിക്കുന്നില്ല.", "keywords": ["website", "database"], "visual_hint": "blocked direct arrow", }, { "scene_id": 3, "type": "example", "duration_seconds": 10, "screen_text": "Website calls API", "voice_text": "അതിനു പകരം website ഒരു API-നെ call ചെയ്യുന്നു.", "keywords": ["website", "API", "call"], "visual_hint": "API middle card", }, { "scene_id": 4, "type": "exam_answer", "duration_seconds": 11, "screen_text": "API returns data", "voice_text": "API server-ൽ നിന്ന് data എടുത്ത് website-ന് തിരിച്ച് കൊടുക്കും.", "keywords": ["server", "data", "website"], "visual_hint": "response arrow", }, { "scene_id": 5, "type": "recap", "duration_seconds": 10, "screen_text": "Middleman idea", "voice_text": "That is why API is like a middleman between frontend and backend.", "keywords": ["middleman", "frontend", "backend"], "visual_hint": "three-node recap", }, ], }, }, "lenses_teacher_mix": { "video_id": "sample-lenses-ai4bharat-teacher", "scene_plan": { "title": "Lenses — AI4Bharat Teacher Voice", "duration_minutes": 1, "language": "English + Malayalam", "style": "kerala_sslc_teacher", "scenes": [ { "scene_id": 1, "type": "concept", "duration_seconds": 16, "screen_text": "Convex lens: thicker at the centre", "voice_text": "A convex lens is thicker at the centre. It bends parallel light rays so that they meet at the principal focus. This is why we call it a converging lens.", "keywords": ["convex lens", "principal focus", "converging"], "visual_hint": "simple labelled convex lens and two parallel rays", }, { "scene_id": 2, "type": "language_support", "duration_seconds": 17, "screen_text": "Remember this", "voice_text": "കോൺവെക്സ് ലെൻസ് നടുവിൽ കട്ടിയുള്ളതാണ്. സമാന്തര രശ്മികൾ ഒരു ബിന്ദുവിലേക്ക് കൂടിച്ചേരും. പരീക്ഷാ ഉത്തരത്തിൽ നടുവിൽ കട്ടിയുള്ളത്, കൺവർജിങ് ലെൻസ്, പ്രിൻസിപ്പൽ ഫോക്കസ് എന്നീ പ്രധാന പദങ്ങൾ എഴുതണം.", "keywords": ["thicker at the centre", "converging lens", "principal focus"], "visual_hint": "three exam keywords beside the same lens", }, ], }, }, } def main() -> None: parser = argparse.ArgumentParser(description="Generate sample Docdeo TTS audio.") parser.add_argument( "--provider", default="mock", help="mock, ai4bharat, kokoro, hybrid, edge, indic", ) parser.add_argument( "--voice-mode", default="english_soft", choices=["english_soft", "malayalam_soft", "ml_en_mix", "lenses_teacher_mix"], ) args = parser.parse_args() sample_key = args.voice_mode if args.voice_mode in SAMPLE_PLANS else "english_soft" sample = SAMPLE_PLANS[sample_key] effective_voice_mode = "ml_en_mix" if args.voice_mode == "lenses_teacher_mix" else args.voice_mode request = GenerateAudioRequest( scene_plan=sample["scene_plan"], voice_mode=effective_voice_mode, voice="teacher_mix" if args.voice_mode in {"ml_en_mix", "lenses_teacher_mix"} else "teacher_english", language=sample["scene_plan"]["language"], provider=args.provider, ) try: response = generate_audio_for_scene_plan( scene_plan=request.scene_plan, voice_mode=request.voice_mode, voice=request.voice, language=request.language, provider_name=request.provider, video_id=sample["video_id"], ) except Exception as exc: status_path = BACKEND_ROOT.parent / "outputs" / "video" / "physics" / "lenses" / "voice-tests" / "ai4bharat-error.json" status_path.parent.mkdir(parents=True, exist_ok=True) status_path.write_text( json.dumps({"ok": False, "error_type": type(exc).__name__, "message": str(exc)}, indent=2, ensure_ascii=False), encoding="utf-8", ) raise props_path = ( BACKEND_ROOT.parent / "public" / "generated" / "audio" / sample["video_id"] / "scene-plan-with-audio.json" ) props_path.write_text( json.dumps({"plan": response["updated_scene_plan"]}, indent=2, ensure_ascii=False), encoding="utf-8", ) print(json.dumps({"ok": True, "props_path": str(props_path), **response}, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()