📦 deps(thirdparty): update snapshots
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate AI voice narration for slides using the 2slides API.
|
||||
Supports single and multi-speaker modes with 30 voice options.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
|
||||
API_BASE_URL = "https://2slides.com/api/v1"
|
||||
|
||||
# Available voice options (30 voices)
|
||||
AVAILABLE_VOICES = [
|
||||
"Puck", "Aoede", "Charon", "Kore", "Fenrir", "Phoebe", "Asteria",
|
||||
"Luna", "Stella", "Theia", "Helios", "Atlas", "Clio", "Melpomene",
|
||||
"Calliope", "Erato", "Euterpe", "Polyhymnia", "Terpsichore", "Thalia",
|
||||
"Urania", "Zeus", "Hera", "Poseidon", "Athena", "Apollo", "Artemis",
|
||||
"Ares", "Aphrodite", "Hephaestus"
|
||||
]
|
||||
|
||||
|
||||
def get_api_key() -> str:
|
||||
"""Get API key from environment variable."""
|
||||
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
||||
"Get your API key from: https://2slides.com/api"
|
||||
)
|
||||
return api_key
|
||||
|
||||
|
||||
def generate_narration(
|
||||
job_id: str,
|
||||
language: str = "Auto",
|
||||
voice: str = "Puck",
|
||||
multi_speaker: bool = False,
|
||||
api_key: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate AI voice narration for slides.
|
||||
|
||||
Args:
|
||||
job_id: Job ID from slide generation (must be UUID format for Nano Banana)
|
||||
language: Language for narration (default: "Auto")
|
||||
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
||||
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
||||
Vietnamese, Turkish, Polish, Italian, Korean
|
||||
voice: Voice name (default: "Puck")
|
||||
Options: Puck, Aoede, Charon, Kore, Fenrir, Phoebe, Asteria, Luna, Stella,
|
||||
Theia, Helios, Atlas, Clio, Melpomene, Calliope, Erato, Euterpe, Polyhymnia,
|
||||
Terpsichore, Thalia, Urania, Zeus, Hera, Poseidon, Athena, Apollo, Artemis,
|
||||
Ares, Aphrodite, Hephaestus
|
||||
multi_speaker: Enable multi-speaker mode (default: False)
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
Dict with narration generation result
|
||||
|
||||
Notes:
|
||||
- Job must be completed before adding narration
|
||||
- Cost: 210 credits per page (10 for text, 200 for audio)
|
||||
- Processing time: Varies by slide count
|
||||
"""
|
||||
if api_key is None:
|
||||
api_key = get_api_key()
|
||||
|
||||
if voice not in AVAILABLE_VOICES:
|
||||
print(f"Warning: Voice '{voice}' not in known voices list", file=sys.stderr)
|
||||
print(f"Available voices: {', '.join(AVAILABLE_VOICES)}", file=sys.stderr)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"jobId": job_id,
|
||||
"language": language,
|
||||
"voice": voice,
|
||||
"multiSpeaker": multi_speaker
|
||||
}
|
||||
|
||||
url = f"{API_BASE_URL}/slides/generate-narration"
|
||||
|
||||
print("Generating voice narration...", file=sys.stderr)
|
||||
print(f"Voice: {voice}, Multi-speaker: {multi_speaker}", file=sys.stderr)
|
||||
|
||||
# Set reasonable timeout for narration generation
|
||||
timeout = 120
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
|
||||
# Check API response structure
|
||||
if not result.get("success"):
|
||||
# Common error example:
|
||||
# {"error":"Job is not completed","code":"JOB_NOT_COMPLETED",...}
|
||||
error_msg = result.get("error", "Unknown error")
|
||||
code = result.get("code")
|
||||
details = result.get("details")
|
||||
extra = f" (code={code})" if code else ""
|
||||
raise ValueError(f"API error: {error_msg}{extra}{f' details={details}' if details else ''}")
|
||||
|
||||
# API may return either:
|
||||
# - { success:true, data:{...} }
|
||||
# - { success:true, jobId:"...", message:"..." } (no data field)
|
||||
data = result.get("data")
|
||||
if not data:
|
||||
data = {
|
||||
"jobId": result.get("jobId") or job_id,
|
||||
"status": result.get("status") or "pending",
|
||||
"message": result.get("message") or "Narration generation started"
|
||||
}
|
||||
|
||||
print("✓ Narration generation started!", file=sys.stderr)
|
||||
print(f" Job ID: {data.get('jobId')}", file=sys.stderr)
|
||||
print("Use get_job_status.py to check progress", file=sys.stderr)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def list_voices():
|
||||
"""Print available voices."""
|
||||
print("Available voices (30 total):")
|
||||
print("-" * 40)
|
||||
for i, voice in enumerate(AVAILABLE_VOICES, 1):
|
||||
print(f"{i:2d}. {voice}")
|
||||
print("-" * 40)
|
||||
print("\nPopular choices: Puck, Aoede, Charon")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate AI voice narration for 2slides presentations",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# List available voices
|
||||
%(prog)s --list-voices
|
||||
|
||||
# Generate narration with default voice
|
||||
%(prog)s --job-id "abc-123-def-456"
|
||||
|
||||
# Generate with specific voice
|
||||
%(prog)s --job-id "abc-123-def-456" --voice "Aoede"
|
||||
|
||||
# Generate with multi-speaker mode
|
||||
%(prog)s --job-id "abc-123-def-456" --multi-speaker
|
||||
|
||||
# Generate in Spanish
|
||||
%(prog)s --job-id "abc-123-def-456" --language "Spanish" --voice "Charon"
|
||||
|
||||
Credit Cost: 210 credits per page (10 for text, 200 for audio)
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--job-id", help="Job ID from slide generation (UUID format)")
|
||||
parser.add_argument("--language", default="Auto", help="Narration language (default: Auto)")
|
||||
parser.add_argument("--voice", default="Puck", help="Voice name (default: Puck)")
|
||||
parser.add_argument("--multi-speaker", action="store_true", help="Enable multi-speaker mode")
|
||||
parser.add_argument("--list-voices", action="store_true", help="List available voices and exit")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_voices:
|
||||
list_voices()
|
||||
return
|
||||
|
||||
if not args.job_id:
|
||||
print("Error: --job-id is required (or use --list-voices)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
result = generate_narration(
|
||||
job_id=args.job_id,
|
||||
language=args.language,
|
||||
voice=args.voice,
|
||||
multi_speaker=args.multi_speaker
|
||||
)
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user