DocDoeAI / scripts /generate_sample_tts.py
asnannp's picture
Deploy backend cd4237ff: support routes + rate limit + exam_date nullable + upload 413 fix
7c6ffa6
Raw
History Blame Contribute Delete
10.2 kB
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()