📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
2slides API allowed parameter values. Aligned with https://2slides.com/api.md
|
||||
"""
|
||||
|
||||
API_BASE_URL = "https://2slides.com/api/v1"
|
||||
|
||||
# responseLanguage (all endpoints that accept it)
|
||||
RESPONSE_LANGUAGES = [
|
||||
"Auto",
|
||||
"English",
|
||||
"Spanish",
|
||||
"Arabic",
|
||||
"Portuguese",
|
||||
"Indonesian",
|
||||
"Japanese",
|
||||
"Russian",
|
||||
"Hindi",
|
||||
"French",
|
||||
"German",
|
||||
"Greek",
|
||||
"Vietnamese",
|
||||
"Turkish",
|
||||
"Polish",
|
||||
"Italian",
|
||||
"Korean",
|
||||
"Simplified Chinese",
|
||||
"Traditional Chinese",
|
||||
"Thai",
|
||||
]
|
||||
|
||||
# aspectRatio (create-like-this, create-pdf-slides)
|
||||
ASPECT_RATIOS = [
|
||||
"1:1",
|
||||
"2:3",
|
||||
"3:2",
|
||||
"3:4",
|
||||
"4:3",
|
||||
"4:5",
|
||||
"5:4",
|
||||
"9:16",
|
||||
"16:9",
|
||||
"21:9",
|
||||
]
|
||||
|
||||
# resolution (create-like-this, create-pdf-slides)
|
||||
RESOLUTIONS = ["1K", "2K", "4K"]
|
||||
|
||||
# contentDetail / contentMode
|
||||
CONTENT_DETAILS = ["concise", "standard"]
|
||||
|
||||
# mode (generate, create-like-this, create-pdf-slides)
|
||||
MODES = ["sync", "async"]
|
||||
|
||||
# generate-narration: Supported Voices (30 total, from API doc)
|
||||
NARRATION_VOICES = [
|
||||
"Puck",
|
||||
"Aoede",
|
||||
"Charon",
|
||||
"Kore",
|
||||
"Fenrir",
|
||||
"Zephyr",
|
||||
"Leda",
|
||||
"Orus",
|
||||
"Callirrhoe",
|
||||
"Autonoe",
|
||||
"Enceladus",
|
||||
"Iapetus",
|
||||
"Umbriel",
|
||||
"Algieba",
|
||||
"Despina",
|
||||
"Erinome",
|
||||
"Algenib",
|
||||
"Rasalgethi",
|
||||
"Laomedeia",
|
||||
"Achernar",
|
||||
"Alnilam",
|
||||
"Schedar",
|
||||
"Gacrux",
|
||||
"Pulcherrima",
|
||||
"Achird",
|
||||
"Zubenelgenubi",
|
||||
"Vindemiatrix",
|
||||
"Sadachbia",
|
||||
"Sadaltager",
|
||||
"Sulafat",
|
||||
]
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate custom-designed slides from text using the 2slides API.
|
||||
Similar to create-like-this but without needing a reference image.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
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 create_pdf_slides(
|
||||
user_input: str,
|
||||
response_language: str = "Auto",
|
||||
aspect_ratio: str = "16:9",
|
||||
resolution: str = "2K",
|
||||
page: int = 1,
|
||||
content_detail: str = "concise",
|
||||
design_spec: Optional[str] = None,
|
||||
api_key: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate custom-designed slides from text with optional design specifications.
|
||||
|
||||
Args:
|
||||
user_input: Content to convert into slides
|
||||
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) or "standard" (detailed) (default: "concise")
|
||||
design_spec: Optional design specifications (e.g., "modern minimalist", "corporate blue")
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
Dict with generation result
|
||||
"""
|
||||
if api_key is None:
|
||||
api_key = get_api_key()
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"userInput": user_input,
|
||||
"responseLanguage": response_language,
|
||||
"aspectRatio": aspect_ratio,
|
||||
"resolution": resolution,
|
||||
"page": page,
|
||||
"contentDetail": content_detail
|
||||
}
|
||||
|
||||
if design_spec:
|
||||
payload["designSpec"] = design_spec
|
||||
|
||||
url = f"{API_BASE_URL}/slides/create-pdf-slides"
|
||||
|
||||
# Calculate dynamic timeout: ~30s per page, minimum 120s
|
||||
timeout = max(120, page * 40)
|
||||
|
||||
print("Generating custom-designed slides...", 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 custom-designed slides using 2slides API",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Generate slides with auto design
|
||||
%(prog)s --content "Sales Report Q4 2025"
|
||||
|
||||
# Generate with specific design
|
||||
%(prog)s --content "Marketing Plan" --design-spec "modern minimalist, blue color scheme"
|
||||
|
||||
# Generate in 4K resolution
|
||||
%(prog)s --content "Product Launch" --resolution 4K --page 5
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--content", required=True, help="Content for slides")
|
||||
parser.add_argument("--design-spec", help="Optional design specifications")
|
||||
parser.add_argument("--language", default="Auto", help="Response language (default: Auto)")
|
||||
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:
|
||||
result = create_pdf_slides(
|
||||
user_input=args.content,
|
||||
response_language=args.language,
|
||||
aspect_ratio=args.aspect_ratio,
|
||||
resolution=args.resolution,
|
||||
page=args.page,
|
||||
content_detail=args.content_detail,
|
||||
design_spec=args.design_spec
|
||||
)
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Download slides pages as PNG files and voice narrations as WAV files.
|
||||
Exports everything as a ZIP archive (completely free).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
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 download_slides_pages_voices(
|
||||
job_id: str,
|
||||
output_path: Optional[str] = None,
|
||||
api_key: Optional[str] = None
|
||||
) -> str:
|
||||
"""
|
||||
Download slides pages and voice narrations as a ZIP archive.
|
||||
|
||||
Args:
|
||||
job_id: Job ID from slide generation
|
||||
output_path: Optional path to save the ZIP file (default: <job_id>.zip)
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
Path to the downloaded ZIP file
|
||||
|
||||
Notes:
|
||||
- Exports pages as PNG files
|
||||
- Exports voices as WAV files
|
||||
- Includes transcripts
|
||||
- Completely free (no credit cost)
|
||||
- Download URL valid for 1 hour
|
||||
"""
|
||||
if api_key is None:
|
||||
api_key = get_api_key()
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"jobId": job_id
|
||||
}
|
||||
|
||||
url = f"{API_BASE_URL}/slides/download-slides-pages-voices"
|
||||
|
||||
print(f"Requesting download for job: {job_id}...", file=sys.stderr)
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
||||
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}")
|
||||
|
||||
# Get download URL from data field
|
||||
data = result.get("data")
|
||||
if not data:
|
||||
raise ValueError("No data in API response")
|
||||
|
||||
download_url = data.get("downloadUrl")
|
||||
if not download_url:
|
||||
raise ValueError("No download URL in response")
|
||||
|
||||
# Optional: log additional info
|
||||
file_name = data.get("fileName", "unknown.zip")
|
||||
expires_in = data.get("expiresIn", 3600)
|
||||
print(f" Filename: {file_name}", file=sys.stderr)
|
||||
print(f" Expires in: {expires_in} seconds", file=sys.stderr)
|
||||
|
||||
# Download the ZIP file
|
||||
if output_path is None:
|
||||
output_path = f"{job_id}.zip"
|
||||
|
||||
print(f"Downloading ZIP archive to: {output_path}...", file=sys.stderr)
|
||||
|
||||
zip_response = requests.get(download_url, stream=True, timeout=120)
|
||||
zip_response.raise_for_status()
|
||||
|
||||
# Save to file
|
||||
with open(output_path, 'wb') as f:
|
||||
for chunk in zip_response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
file_size = os.path.getsize(output_path)
|
||||
print(f"✓ Downloaded successfully!", file=sys.stderr)
|
||||
print(f" File: {output_path}", file=sys.stderr)
|
||||
print(f" Size: {file_size:,} bytes", file=sys.stderr)
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download 2slides pages and voices as ZIP archive (FREE)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Download with default filename
|
||||
%(prog)s --job-id "abc-123-def-456"
|
||||
|
||||
# Download to specific path
|
||||
%(prog)s --job-id "abc-123-def-456" --output slides.zip
|
||||
|
||||
Archive Contents:
|
||||
- Pages as PNG files
|
||||
- Voice files as WAV
|
||||
- Transcripts
|
||||
|
||||
Note: Download URLs are valid for 1 hour only
|
||||
Cost: Completely FREE (no credits used)
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--job-id", required=True, help="Job ID from slide generation")
|
||||
parser.add_argument("--output", help="Output ZIP file path (default: <job_id>.zip)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
output_path = download_slides_pages_voices(
|
||||
job_id=args.job_id,
|
||||
output_path=args.output
|
||||
)
|
||||
|
||||
# Output path for easy parsing
|
||||
print(json.dumps({"success": True, "output": output_path}, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+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()
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
#!/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 time
|
||||
import argparse
|
||||
import requests
|
||||
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
|
||||
"""
|
||||
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",
|
||||
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")
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
Dict with generation result
|
||||
"""
|
||||
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
|
||||
}
|
||||
|
||||
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"], default="sync",
|
||||
help="Generation mode (default: sync)")
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check the status of an async slide generation job.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
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 get_job_status(
|
||||
job_id: str,
|
||||
api_key: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get the status of a slide generation job.
|
||||
|
||||
Args:
|
||||
job_id: Job ID from async generation
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
Dict with job status and result
|
||||
"""
|
||||
if api_key is None:
|
||||
api_key = get_api_key()
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
url = f"{API_BASE_URL}/jobs/{job_id}"
|
||||
|
||||
print(f"Checking job status: {job_id}...", file=sys.stderr)
|
||||
response = requests.get(url, headers=headers)
|
||||
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")
|
||||
|
||||
status = data.get("status", "unknown")
|
||||
|
||||
print(f"✓ Job status: {status}", file=sys.stderr)
|
||||
if data.get("message"):
|
||||
print(f" Message: {data.get('message')}", file=sys.stderr)
|
||||
if data.get("slidePageCount"):
|
||||
print(f" Pages: {data.get('slidePageCount')}", file=sys.stderr)
|
||||
if data.get("downloadUrl"):
|
||||
print(f" Download URL: {data.get('downloadUrl')}", file=sys.stderr)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check 2slides job status",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Check job status
|
||||
%(prog)s --job-id abc123
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--job-id", required=True, help="Job ID to check")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
result = get_job_status(job_id=args.job_id)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Search for available themes in the 2slides catalog.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import requests
|
||||
from typing import Optional, List, 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 search_themes(
|
||||
query: str,
|
||||
limit: int = 20,
|
||||
api_key: Optional[str] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search for themes.
|
||||
|
||||
Args:
|
||||
query: Search query (required keyword)
|
||||
limit: Maximum number of results (max 100, default 20)
|
||||
api_key: API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
List of theme objects
|
||||
"""
|
||||
if api_key is None:
|
||||
api_key = get_api_key()
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
params = {
|
||||
"query": query,
|
||||
"limit": min(limit, 100)
|
||||
}
|
||||
|
||||
url = f"{API_BASE_URL}/themes/search"
|
||||
|
||||
print(f"Searching themes{f': {query}' if query else ''}...", file=sys.stderr)
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
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")
|
||||
|
||||
themes = data.get("themes", [])
|
||||
|
||||
print(f"✓ Found {len(themes)} theme(s)", file=sys.stderr)
|
||||
|
||||
return themes
|
||||
|
||||
|
||||
def format_theme(theme: Dict[str, Any]) -> str:
|
||||
"""Format a theme object for display."""
|
||||
theme_id = theme.get("id", "N/A")
|
||||
name = theme.get("name", "Unnamed")
|
||||
description = theme.get("description", "No description")
|
||||
|
||||
return f"ID: {theme_id}\nName: {name}\nDescription: {description}\n"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Search for 2slides themes",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Search for business themes
|
||||
%(prog)s --query "business"
|
||||
|
||||
# Search for creative themes
|
||||
%(prog)s --query "creative"
|
||||
|
||||
# Get more results
|
||||
%(prog)s --query "professional" --limit 50
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--query", required=True, help="Search query (required keyword)")
|
||||
parser.add_argument("--limit", type=int, default=20,
|
||||
help="Maximum results (max 100, default 20)")
|
||||
parser.add_argument("--json", action="store_true",
|
||||
help="Output raw JSON")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
themes = search_themes(
|
||||
query=args.query,
|
||||
limit=args.limit
|
||||
)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(themes, indent=2))
|
||||
else:
|
||||
print()
|
||||
for i, theme in enumerate(themes, 1):
|
||||
print(f"Theme {i}:")
|
||||
print(format_theme(theme))
|
||||
print("-" * 60)
|
||||
|
||||
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