fix: correct ELAI API payload format for video generation

Use proper slide structure with canvas, avatar object, and voice settings
to match ELAI API requirements.

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-12 02:47:53 +04:00
parent 69d8a7c52f
commit c661a187e7
118 changed files with 58 additions and 2 deletions

View File

@@ -12,6 +12,14 @@ except ImportError:
ELAI_BASE = "https://apis.elai.io/api/v1"
AVATAR_PRESETS = {
"vadim": {"code": "vadim.casual", "gender": "male", "voice": "en-GB-RyanNeural"},
"gia": {"code": "gia.casual", "gender": "female", "voice": "en-US-AriaNeural"},
"zara": {"code": "zara.regular", "gender": "female", "voice": "en-US-JennyNeural"},
"mason": {"code": "mason.casual", "gender": "male", "voice": "en-US-GuyNeural"},
"default": {"code": "gia.casual", "gender": "female", "voice": "en-US-AriaNeural"},
}
class ElaiService:
"""Generate avatar videos for listening exercises and instructional content."""
@@ -33,6 +41,7 @@ class ElaiService:
return {
"Authorization": f"Bearer {self._get_token()}",
"Content-Type": "application/json",
"Accept": "application/json",
}
def _log(self, action, latency, status="success", error=None):
@@ -47,6 +56,14 @@ class ElaiService:
except Exception:
pass
def _resolve_avatar(self, avatar_id):
if not avatar_id or avatar_id == "default":
return AVATAR_PRESETS["default"]
key = avatar_id.split(".")[0].lower() if "." in avatar_id else avatar_id.lower()
if key in AVATAR_PRESETS:
return AVATAR_PRESETS[key]
return {"code": avatar_id, "gender": "female", "voice": "en-US-AriaNeural"}
def list_avatars(self):
"""List available ELAI avatars."""
if not _requests:
@@ -63,15 +80,45 @@ class ElaiService:
"""
if not _requests:
raise RuntimeError("requests package not installed")
avatar_preset = self._resolve_avatar(avatar_id)
avatar_code = avatar_preset["code"]
avatar_src = f"https://elai-avatars.s3.us-east-2.amazonaws.com/common/{avatar_code.replace('.', '/')}/{avatar_code.replace('.', '_')}.png"
payload = {
"name": title,
"slides": [
{
"id": 1,
"speech": script,
"avatar": avatar_id or "default",
"language": language,
"language": "English",
"voice": avatar_preset["voice"],
"voiceType": "text",
"voiceProvider": "azure",
"avatar": {
"code": avatar_code,
"gender": avatar_preset["gender"],
},
"canvas": {
"version": "4.4.0",
"background": "#ffffff",
"objects": [
{
"type": "avatar",
"left": 151.5,
"top": 36,
"fill": "#4868FF",
"scaleX": 0.3,
"scaleY": 0.3,
"height": 1080,
"width": 1080,
"src": avatar_src,
}
],
},
}
],
"tags": ["encoach"],
}
t0 = time.time()
try:
@@ -85,6 +132,15 @@ class ElaiService:
data = resp.json()
self._log("create_video", int((time.time() - t0) * 1000))
return {"video_id": data.get("_id", data.get("id")), "status": data.get("status", "pending")}
except _requests.exceptions.HTTPError as exc:
body = ""
try:
body = exc.response.text[:500]
except Exception:
pass
_logger.error("ELAI create_video failed: %s%s", exc, body)
self._log("create_video", int((time.time() - t0) * 1000), "error", f"{exc} | {body}")
raise RuntimeError(f"ELAI API error: {exc.response.status_code}{body}") from exc
except Exception as exc:
self._log("create_video", int((time.time() - t0) * 1000), "error", str(exc))
raise

Some files were not shown because too many files have changed in this diff Show More