254 lines
8.7 KiB
Python
Executable File
254 lines
8.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate slides using the 2slides API.
|
|
Supports both content-based and reference image-based generation.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
API_BASE_URL = "https://2slides.com/api/v1"
|
|
|
|
|
|
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_slides(
|
|
user_input: str,
|
|
theme_id: str,
|
|
response_language: str = "Auto",
|
|
mode: str = "sync",
|
|
api_key: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate slides from user input.
|
|
|
|
Args:
|
|
user_input: Content to convert into slides
|
|
theme_id: Theme ID (required, use search_themes.py to find themes)
|
|
response_language: Language (default: "Auto")
|
|
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
Vietnamese, Turkish, Polish, Italian, Korean
|
|
mode: "sync" or "async" (default: "sync")
|
|
api_key: API key (uses env var if not provided)
|
|
|
|
Returns:
|
|
Dict with generation result or job ID
|
|
"""
|
|
import requests
|
|
|
|
if api_key is None:
|
|
api_key = get_api_key()
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
payload = {
|
|
"userInput": user_input,
|
|
"themeId": theme_id,
|
|
"responseLanguage": response_language,
|
|
"mode": mode
|
|
}
|
|
|
|
url = f"{API_BASE_URL}/slides/generate"
|
|
|
|
# Set timeout: 90s for sync (waits for completion), 30s for async (just creates job)
|
|
timeout = 90 if mode == "sync" else 30
|
|
|
|
print(f"Generating slides in {mode} mode...", file=sys.stderr)
|
|
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"):
|
|
error_msg = result.get("error", "Unknown error")
|
|
raise ValueError(f"API error: {error_msg}")
|
|
|
|
# Extract data from response
|
|
data = result.get("data")
|
|
if not data:
|
|
raise ValueError("No data in API response")
|
|
|
|
if mode == "sync":
|
|
print("✓ Slides generated successfully!", file=sys.stderr)
|
|
print(f" Pages: {data.get('slidePageCount', 'N/A')}", file=sys.stderr)
|
|
if data.get("downloadUrl"):
|
|
print(f" Download URL: {data.get('downloadUrl')}", file=sys.stderr)
|
|
else:
|
|
print(f"✓ Job created: {data.get('jobId')}", file=sys.stderr)
|
|
print("Use get_job_status.py to check status", file=sys.stderr)
|
|
|
|
return data
|
|
|
|
|
|
def create_like_this(
|
|
user_input: str,
|
|
reference_image_url: str,
|
|
response_language: str = "Auto",
|
|
aspect_ratio: str = "16:9",
|
|
resolution: str = "2K",
|
|
page: int = 1,
|
|
content_detail: str = "concise",
|
|
mode: str = "async",
|
|
api_key: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate slides matching a reference image style (Nano Banana Pro).
|
|
|
|
Args:
|
|
user_input: Content to convert into slides
|
|
reference_image_url: URL or base64 of reference image to match style
|
|
response_language: Language (default: "Auto")
|
|
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
Vietnamese, Turkish, Polish, Italian, Korean
|
|
aspect_ratio: Aspect ratio in width:height format (default: "16:9")
|
|
resolution: Output quality - "1K", "2K", or "4K" (default: "2K")
|
|
page: Number of slides, 0 for auto-detection, max 100 (default: 1)
|
|
content_detail: "concise" (brief, keyword-focused) or "standard" (comprehensive) (default: "concise")
|
|
mode: "sync" or "async" (default: "async")
|
|
api_key: API key (uses env var if not provided)
|
|
|
|
Returns:
|
|
Dict with generation result
|
|
"""
|
|
import requests
|
|
|
|
if api_key is None:
|
|
api_key = get_api_key()
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
payload = {
|
|
"userInput": user_input,
|
|
"referenceImageUrl": reference_image_url,
|
|
"responseLanguage": response_language,
|
|
"aspectRatio": aspect_ratio,
|
|
"resolution": resolution,
|
|
"page": page,
|
|
"contentDetail": content_detail,
|
|
"mode": mode
|
|
}
|
|
|
|
url = f"{API_BASE_URL}/slides/create-like-this"
|
|
|
|
# Calculate dynamic timeout: ~30s per page, minimum 120s
|
|
timeout = max(120, page * 40)
|
|
|
|
print("Generating slides from reference image...", file=sys.stderr)
|
|
print(f"(Timeout set to {timeout}s for {page} page(s))", file=sys.stderr)
|
|
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
response.raise_for_status()
|
|
|
|
result = response.json()
|
|
|
|
# Handle the actual API response structure
|
|
if result.get("success") and "data" in result:
|
|
data = result["data"]
|
|
# Transform to expected format for consistency
|
|
normalized_result = {
|
|
"slideUrl": data.get("jobUrl"),
|
|
"pdfUrl": data.get("downloadUrl"),
|
|
"status": "completed" if data.get("status") == "success" else data.get("status"),
|
|
"message": data.get("message"),
|
|
"slidePageCount": data.get("slidePageCount"),
|
|
"jobId": data.get("jobId")
|
|
}
|
|
print("✓ Slides generated successfully!", file=sys.stderr)
|
|
print(f" Pages: {data.get('slidePageCount')}", file=sys.stderr)
|
|
return normalized_result
|
|
else:
|
|
# Fallback to raw result if structure is unexpected
|
|
print("✓ Request completed!", file=sys.stderr)
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate slides using 2slides API",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Generate slides from content
|
|
%(prog)s --content "Intro to AI: ML, Deep Learning, Neural Networks"
|
|
|
|
# Generate with specific theme
|
|
%(prog)s --content "Business Plan" --theme-id "theme123"
|
|
|
|
# Generate in async mode
|
|
%(prog)s --content "Long presentation" --mode async
|
|
|
|
# Generate from reference image
|
|
%(prog)s --content "Sales Report" --reference-image "https://example.com/image.jpg"
|
|
"""
|
|
)
|
|
|
|
parser.add_argument("--content", required=True, help="Content for slides")
|
|
parser.add_argument("--theme-id", help="Theme ID (required for standard generation)")
|
|
parser.add_argument("--reference-image", help="Reference image URL (use this OR theme-id)")
|
|
parser.add_argument("--language", default="Auto", help="Response language (default: Auto)")
|
|
parser.add_argument("--mode", choices=["sync", "async"],
|
|
help="Generation mode (default: sync for theme, async for reference-image)")
|
|
parser.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio in width:height format (default: 16:9)")
|
|
parser.add_argument("--resolution", choices=["1K", "2K", "4K"], default="2K",
|
|
help="Output quality (default: 2K)")
|
|
parser.add_argument("--page", type=int, default=1, help="Number of slides, 0 for auto (default: 1, max: 100)")
|
|
parser.add_argument("--content-detail", choices=["concise", "standard"], default="concise",
|
|
help="Content detail level (default: concise)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
if args.reference_image:
|
|
result = create_like_this(
|
|
user_input=args.content,
|
|
reference_image_url=args.reference_image,
|
|
response_language=args.language,
|
|
aspect_ratio=args.aspect_ratio,
|
|
resolution=args.resolution,
|
|
page=args.page,
|
|
content_detail=args.content_detail,
|
|
mode=args.mode or "async"
|
|
)
|
|
else:
|
|
if not args.theme_id:
|
|
print("Error: --theme-id is required for standard generation", file=sys.stderr)
|
|
print("Use --reference-image for style-based generation instead", file=sys.stderr)
|
|
sys.exit(1)
|
|
result = generate_slides(
|
|
user_input=args.content,
|
|
theme_id=args.theme_id,
|
|
response_language=args.language,
|
|
mode=args.mode or "sync"
|
|
)
|
|
|
|
print(json.dumps(result, indent=2))
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|