Files

240 lines
6.5 KiB
Python

"""
ElevenLabs Video Translation/Dubbing Module
Uses ElevenLabs Dubbing API to translate video audio to different languages.
"""
import os
import time
import httpx
from typing import Optional
ELEVENLABS_API_BASE = "https://api.elevenlabs.io/v1"
# Supported target languages for dubbing
SUPPORTED_LANGUAGES = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"it": "Italian",
"pt": "Portuguese",
"pl": "Polish",
"hi": "Hindi",
"ja": "Japanese",
"ko": "Korean",
"zh": "Chinese",
"ar": "Arabic",
"ru": "Russian",
"tr": "Turkish",
"nl": "Dutch",
"sv": "Swedish",
"id": "Indonesian",
"fil": "Filipino",
"ms": "Malay",
"vi": "Vietnamese",
"th": "Thai",
"uk": "Ukrainian",
"el": "Greek",
"cs": "Czech",
"fi": "Finnish",
"ro": "Romanian",
"da": "Danish",
"bg": "Bulgarian",
"hr": "Croatian",
"sk": "Slovak",
"ta": "Tamil",
}
def create_dubbing_project(
video_path: str,
target_language: str,
api_key: str,
source_language: Optional[str] = None,
) -> dict:
"""
Create a new dubbing project with ElevenLabs.
Args:
video_path: Path to the video file
target_language: Target language code (e.g., 'es', 'fr', 'de')
api_key: ElevenLabs API key
source_language: Source language code (auto-detected if None)
Returns:
dict with dubbing_id and expected_duration_sec
"""
url = f"{ELEVENLABS_API_BASE}/dubbing"
headers = {
"xi-api-key": api_key,
}
# Prepare form data
data = {
"target_lang": target_language,
"mode": "automatic",
"num_speakers": "0",
"watermark": "false",
}
if source_language:
data["source_lang"] = source_language
# Open and send the video file
with open(video_path, "rb") as video_file:
files = {
"file": (os.path.basename(video_path), video_file, "video/mp4")
}
print(f"[ElevenLabs] Creating dubbing project for {target_language}...")
with httpx.Client(timeout=300.0) as client:
response = client.post(url, headers=headers, data=data, files=files)
if response.status_code not in [200, 201]:
error_msg = response.text
try:
error_data = response.json()
error_msg = error_data.get("detail", {}).get("message", response.text)
except:
pass
raise Exception(f"ElevenLabs API error: {error_msg}")
result = response.json()
print(f"[ElevenLabs] Dubbing project created: {result.get('dubbing_id')}")
return result
def get_dubbing_status(dubbing_id: str, api_key: str) -> dict:
"""
Check the status of a dubbing project.
Returns:
dict with status ('dubbing', 'dubbed', 'failed') and other metadata
"""
url = f"{ELEVENLABS_API_BASE}/dubbing/{dubbing_id}"
headers = {
"xi-api-key": api_key,
}
with httpx.Client(timeout=30.0) as client:
response = client.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to get dubbing status: {response.text}")
return response.json()
def download_dubbed_video(
dubbing_id: str,
target_language: str,
output_path: str,
api_key: str
) -> str:
"""
Download the dubbed video file.
Args:
dubbing_id: The dubbing project ID
target_language: Target language code
output_path: Where to save the dubbed video
api_key: ElevenLabs API key
Returns:
Path to the downloaded file
"""
url = f"{ELEVENLABS_API_BASE}/dubbing/{dubbing_id}/audio/{target_language}"
headers = {
"xi-api-key": api_key,
}
print(f"[ElevenLabs] Downloading dubbed video...")
with httpx.Client(timeout=120.0) as client:
with client.stream("GET", url, headers=headers) as response:
if response.status_code != 200:
raise Exception(f"Failed to download dubbed video: {response.text}")
with open(output_path, "wb") as f:
for chunk in response.iter_bytes(chunk_size=8192):
f.write(chunk)
print(f"[ElevenLabs] Dubbed video saved to: {output_path}")
return output_path
def translate_video(
video_path: str,
output_path: str,
target_language: str,
api_key: str,
source_language: Optional[str] = None,
max_wait_seconds: int = 600,
poll_interval: int = 5,
) -> str:
"""
Translate a video to a target language using ElevenLabs dubbing.
This is a blocking call that waits for the dubbing to complete.
Args:
video_path: Path to input video
output_path: Path to save translated video
target_language: Target language code
api_key: ElevenLabs API key
source_language: Source language code (auto-detected if None)
max_wait_seconds: Maximum time to wait for dubbing (default 10 min)
poll_interval: Seconds between status checks
Returns:
Path to the translated video
"""
# Create dubbing project
project = create_dubbing_project(
video_path=video_path,
target_language=target_language,
api_key=api_key,
source_language=source_language,
)
dubbing_id = project["dubbing_id"]
expected_duration = project.get("expected_duration_sec", 60)
print(f"[ElevenLabs] Dubbing ID: {dubbing_id}, Expected duration: {expected_duration}s")
# Poll for completion
start_time = time.time()
while True:
elapsed = time.time() - start_time
if elapsed > max_wait_seconds:
raise Exception(f"Dubbing timed out after {max_wait_seconds} seconds")
status = get_dubbing_status(dubbing_id, api_key)
current_status = status.get("status", "unknown")
print(f"[ElevenLabs] Status: {current_status} (elapsed: {int(elapsed)}s)")
if current_status == "dubbed":
# Download the result
return download_dubbed_video(
dubbing_id=dubbing_id,
target_language=target_language,
output_path=output_path,
api_key=api_key,
)
elif current_status == "failed":
error = status.get("error", "Unknown error")
raise Exception(f"Dubbing failed: {error}")
# Still processing, wait and poll again
time.sleep(poll_interval)
def get_supported_languages() -> dict:
"""Return dict of supported language codes and names."""
return SUPPORTED_LANGUAGES.copy()