opencode-skills-collection 3.0.37 → 3.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +13 -1
- package/bundled-skills/2slides-ppt-generator/SKILL.md +786 -0
- package/bundled-skills/2slides-ppt-generator/references/api-reference.md +499 -0
- package/bundled-skills/2slides-ppt-generator/references/mcp-integration.md +282 -0
- package/bundled-skills/2slides-ppt-generator/references/pricing.md +195 -0
- package/bundled-skills/2slides-ppt-generator/scripts/api_constants.py +87 -0
- package/bundled-skills/2slides-ppt-generator/scripts/create_pdf_slides.py +159 -0
- package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +157 -0
- package/bundled-skills/2slides-ppt-generator/scripts/generate_narration.py +197 -0
- package/bundled-skills/2slides-ppt-generator/scripts/generate_slides.py +247 -0
- package/bundled-skills/2slides-ppt-generator/scripts/get_job_status.py +106 -0
- package/bundled-skills/2slides-ppt-generator/scripts/search_themes.py +137 -0
- package/bundled-skills/anti-sycophancy/README.md +86 -0
- package/bundled-skills/anti-sycophancy/SKILL.md +40 -0
- package/bundled-skills/antigravity-agent-manager/SKILL.md +112 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/sources/sources.md +1 -0
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/event-staffing-compliance/SKILL.md +91 -0
- package/bundled-skills/event-staffing-ordering/SKILL.md +119 -0
- package/bundled-skills/examprep-ai/SKILL.md +446 -0
- package/bundled-skills/hasdata/SKILL.md +107 -0
- package/bundled-skills/hasdata/references/code-recipes.md +150 -0
- package/bundled-skills/hasdata/references/ecommerce.md +116 -0
- package/bundled-skills/hasdata/references/jobs.md +111 -0
- package/bundled-skills/hasdata/references/local-business.md +145 -0
- package/bundled-skills/hasdata/references/real-estate.md +84 -0
- package/bundled-skills/hasdata/references/scraper-jobs.md +252 -0
- package/bundled-skills/hasdata/references/search.md +154 -0
- package/bundled-skills/hasdata/references/travel.md +202 -0
- package/bundled-skills/hasdata/references/web-scraping.md +159 -0
- package/bundled-skills/hasdata/references/youtube.md +186 -0
- package/bundled-skills/hasdata-cli/SKILL.md +169 -0
- package/bundled-skills/hasdata-cli/references/all-commands.md +107 -0
- package/bundled-skills/hasdata-cli/references/ecommerce.md +106 -0
- package/bundled-skills/hasdata-cli/references/enrichment.md +227 -0
- package/bundled-skills/hasdata-cli/references/jobs.md +84 -0
- package/bundled-skills/hasdata-cli/references/local-business.md +123 -0
- package/bundled-skills/hasdata-cli/references/real-estate.md +126 -0
- package/bundled-skills/hasdata-cli/references/search.md +122 -0
- package/bundled-skills/hasdata-cli/references/travel.md +102 -0
- package/bundled-skills/hasdata-cli/references/web-scraping.md +181 -0
- package/bundled-skills/hasdata-cli/references/youtube.md +145 -0
- package/bundled-skills/linkedin-content-generator/SKILL.md +492 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_calendar.py +82 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_carousel.py +69 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_newsletter.py +64 -0
- package/bundled-skills/linkedin-content-generator/scripts/generate_post.py +77 -0
- package/bundled-skills/linkedin-content-generator/scripts/memory.md +49 -0
- package/bundled-skills/linkedin-content-generator/scripts/memory_manager.py +134 -0
- package/bundled-skills/linkedin-content-generator/scripts/utils.py +96 -0
- package/bundled-skills/permission-manager/README.md +22 -0
- package/bundled-skills/permission-manager/SKILL.md +54 -0
- package/bundled-skills/skill-suggester/README.md +14 -0
- package/bundled-skills/skill-suggester/SKILL.md +69 -0
- package/bundled-skills/smart-git-automation/README.md +31 -0
- package/bundled-skills/smart-git-automation/SKILL.md +96 -0
- package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +3 -1
- package/bundled-skills/vercel-optimize/lib/render-report.mjs +2 -2
- package/bundled-skills/vercel-optimize/lib/util.mjs +7 -0
- package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +2 -7
- package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +2 -1
- package/package.json +1 -1
- package/skills_index.json +268 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate custom-designed slides from text using the 2slides API.
|
|
4
|
+
Similar to create-like-this but without needing a reference image.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
import requests
|
|
12
|
+
from typing import Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_api_key() -> str:
|
|
19
|
+
"""Get API key from environment variable."""
|
|
20
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
21
|
+
if not api_key:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
24
|
+
"Get your API key from: https://2slides.com/api"
|
|
25
|
+
)
|
|
26
|
+
return api_key
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_pdf_slides(
|
|
30
|
+
user_input: str,
|
|
31
|
+
response_language: str = "Auto",
|
|
32
|
+
aspect_ratio: str = "16:9",
|
|
33
|
+
resolution: str = "2K",
|
|
34
|
+
page: int = 1,
|
|
35
|
+
content_detail: str = "concise",
|
|
36
|
+
design_spec: Optional[str] = None,
|
|
37
|
+
api_key: Optional[str] = None
|
|
38
|
+
) -> Dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Generate custom-designed slides from text with optional design specifications.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
user_input: Content to convert into slides
|
|
44
|
+
response_language: Language (default: "Auto")
|
|
45
|
+
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
46
|
+
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
47
|
+
Vietnamese, Turkish, Polish, Italian, Korean
|
|
48
|
+
aspect_ratio: Aspect ratio in width:height format (default: "16:9")
|
|
49
|
+
resolution: Output quality - "1K", "2K", or "4K" (default: "2K")
|
|
50
|
+
page: Number of slides, 0 for auto-detection, max 100 (default: 1)
|
|
51
|
+
content_detail: "concise" (brief) or "standard" (detailed) (default: "concise")
|
|
52
|
+
design_spec: Optional design specifications (e.g., "modern minimalist", "corporate blue")
|
|
53
|
+
api_key: API key (uses env var if not provided)
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dict with generation result
|
|
57
|
+
"""
|
|
58
|
+
if api_key is None:
|
|
59
|
+
api_key = get_api_key()
|
|
60
|
+
|
|
61
|
+
headers = {
|
|
62
|
+
"Authorization": f"Bearer {api_key}",
|
|
63
|
+
"Content-Type": "application/json"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
payload = {
|
|
67
|
+
"userInput": user_input,
|
|
68
|
+
"responseLanguage": response_language,
|
|
69
|
+
"aspectRatio": aspect_ratio,
|
|
70
|
+
"resolution": resolution,
|
|
71
|
+
"page": page,
|
|
72
|
+
"contentDetail": content_detail
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if design_spec:
|
|
76
|
+
payload["designSpec"] = design_spec
|
|
77
|
+
|
|
78
|
+
url = f"{API_BASE_URL}/slides/create-pdf-slides"
|
|
79
|
+
|
|
80
|
+
# Calculate dynamic timeout: ~30s per page, minimum 120s
|
|
81
|
+
timeout = max(120, page * 40)
|
|
82
|
+
|
|
83
|
+
print("Generating custom-designed slides...", file=sys.stderr)
|
|
84
|
+
print(f"(Timeout set to {timeout}s for {page} page(s))", file=sys.stderr)
|
|
85
|
+
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
86
|
+
response.raise_for_status()
|
|
87
|
+
|
|
88
|
+
result = response.json()
|
|
89
|
+
|
|
90
|
+
# Handle the actual API response structure
|
|
91
|
+
if result.get("success") and "data" in result:
|
|
92
|
+
data = result["data"]
|
|
93
|
+
# Transform to expected format for consistency
|
|
94
|
+
normalized_result = {
|
|
95
|
+
"slideUrl": data.get("jobUrl"),
|
|
96
|
+
"pdfUrl": data.get("downloadUrl"),
|
|
97
|
+
"status": "completed" if data.get("status") == "success" else data.get("status"),
|
|
98
|
+
"message": data.get("message"),
|
|
99
|
+
"slidePageCount": data.get("slidePageCount"),
|
|
100
|
+
"jobId": data.get("jobId")
|
|
101
|
+
}
|
|
102
|
+
print("✓ Slides generated successfully!", file=sys.stderr)
|
|
103
|
+
print(f" Pages: {data.get('slidePageCount')}", file=sys.stderr)
|
|
104
|
+
return normalized_result
|
|
105
|
+
else:
|
|
106
|
+
# Fallback to raw result if structure is unexpected
|
|
107
|
+
print("✓ Request completed!", file=sys.stderr)
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main():
|
|
112
|
+
parser = argparse.ArgumentParser(
|
|
113
|
+
description="Generate custom-designed slides using 2slides API",
|
|
114
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
115
|
+
epilog="""
|
|
116
|
+
Examples:
|
|
117
|
+
# Generate slides with auto design
|
|
118
|
+
%(prog)s --content "Sales Report Q4 2025"
|
|
119
|
+
|
|
120
|
+
# Generate with specific design
|
|
121
|
+
%(prog)s --content "Marketing Plan" --design-spec "modern minimalist, blue color scheme"
|
|
122
|
+
|
|
123
|
+
# Generate in 4K resolution
|
|
124
|
+
%(prog)s --content "Product Launch" --resolution 4K --page 5
|
|
125
|
+
"""
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
parser.add_argument("--content", required=True, help="Content for slides")
|
|
129
|
+
parser.add_argument("--design-spec", help="Optional design specifications")
|
|
130
|
+
parser.add_argument("--language", default="Auto", help="Response language (default: Auto)")
|
|
131
|
+
parser.add_argument("--aspect-ratio", default="16:9", help="Aspect ratio in width:height format (default: 16:9)")
|
|
132
|
+
parser.add_argument("--resolution", choices=["1K", "2K", "4K"], default="2K",
|
|
133
|
+
help="Output quality (default: 2K)")
|
|
134
|
+
parser.add_argument("--page", type=int, default=1, help="Number of slides, 0 for auto (default: 1, max: 100)")
|
|
135
|
+
parser.add_argument("--content-detail", choices=["concise", "standard"], default="concise",
|
|
136
|
+
help="Content detail level (default: concise)")
|
|
137
|
+
|
|
138
|
+
args = parser.parse_args()
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
result = create_pdf_slides(
|
|
142
|
+
user_input=args.content,
|
|
143
|
+
response_language=args.language,
|
|
144
|
+
aspect_ratio=args.aspect_ratio,
|
|
145
|
+
resolution=args.resolution,
|
|
146
|
+
page=args.page,
|
|
147
|
+
content_detail=args.content_detail,
|
|
148
|
+
design_spec=args.design_spec
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
print(json.dumps(result, indent=2))
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
main()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Download slides pages as PNG files and voice narrations as WAV files.
|
|
4
|
+
Exports everything as a ZIP archive (completely free).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
import requests
|
|
12
|
+
from typing import Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_api_key() -> str:
|
|
19
|
+
"""Get API key from environment variable."""
|
|
20
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
21
|
+
if not api_key:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
24
|
+
"Get your API key from: https://2slides.com/api"
|
|
25
|
+
)
|
|
26
|
+
return api_key
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def download_slides_pages_voices(
|
|
30
|
+
job_id: str,
|
|
31
|
+
output_path: Optional[str] = None,
|
|
32
|
+
api_key: Optional[str] = None
|
|
33
|
+
) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Download slides pages and voice narrations as a ZIP archive.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
job_id: Job ID from slide generation
|
|
39
|
+
output_path: Optional path to save the ZIP file (default: <job_id>.zip)
|
|
40
|
+
api_key: API key (uses env var if not provided)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Path to the downloaded ZIP file
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
- Exports pages as PNG files
|
|
47
|
+
- Exports voices as WAV files
|
|
48
|
+
- Includes transcripts
|
|
49
|
+
- Completely free (no credit cost)
|
|
50
|
+
- Download URL valid for 1 hour
|
|
51
|
+
"""
|
|
52
|
+
if api_key is None:
|
|
53
|
+
api_key = get_api_key()
|
|
54
|
+
|
|
55
|
+
headers = {
|
|
56
|
+
"Authorization": f"Bearer {api_key}",
|
|
57
|
+
"Content-Type": "application/json"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
payload = {
|
|
61
|
+
"jobId": job_id
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
url = f"{API_BASE_URL}/slides/download-slides-pages-voices"
|
|
65
|
+
|
|
66
|
+
print(f"Requesting download for job: {job_id}...", file=sys.stderr)
|
|
67
|
+
|
|
68
|
+
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
|
|
71
|
+
result = response.json()
|
|
72
|
+
|
|
73
|
+
# Check API response structure
|
|
74
|
+
if not result.get("success"):
|
|
75
|
+
error_msg = result.get("error", "Unknown error")
|
|
76
|
+
raise ValueError(f"API error: {error_msg}")
|
|
77
|
+
|
|
78
|
+
# Get download URL from data field
|
|
79
|
+
data = result.get("data")
|
|
80
|
+
if not data:
|
|
81
|
+
raise ValueError("No data in API response")
|
|
82
|
+
|
|
83
|
+
download_url = data.get("downloadUrl")
|
|
84
|
+
if not download_url:
|
|
85
|
+
raise ValueError("No download URL in response")
|
|
86
|
+
|
|
87
|
+
# Optional: log additional info
|
|
88
|
+
file_name = data.get("fileName", "unknown.zip")
|
|
89
|
+
expires_in = data.get("expiresIn", 3600)
|
|
90
|
+
print(f" Filename: {file_name}", file=sys.stderr)
|
|
91
|
+
print(f" Expires in: {expires_in} seconds", file=sys.stderr)
|
|
92
|
+
|
|
93
|
+
# Download the ZIP file
|
|
94
|
+
if output_path is None:
|
|
95
|
+
output_path = f"{job_id}.zip"
|
|
96
|
+
|
|
97
|
+
print(f"Downloading ZIP archive to: {output_path}...", file=sys.stderr)
|
|
98
|
+
|
|
99
|
+
zip_response = requests.get(download_url, stream=True, timeout=120)
|
|
100
|
+
zip_response.raise_for_status()
|
|
101
|
+
|
|
102
|
+
# Save to file
|
|
103
|
+
with open(output_path, 'wb') as f:
|
|
104
|
+
for chunk in zip_response.iter_content(chunk_size=8192):
|
|
105
|
+
f.write(chunk)
|
|
106
|
+
|
|
107
|
+
file_size = os.path.getsize(output_path)
|
|
108
|
+
print(f"✓ Downloaded successfully!", file=sys.stderr)
|
|
109
|
+
print(f" File: {output_path}", file=sys.stderr)
|
|
110
|
+
print(f" Size: {file_size:,} bytes", file=sys.stderr)
|
|
111
|
+
|
|
112
|
+
return output_path
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main():
|
|
116
|
+
parser = argparse.ArgumentParser(
|
|
117
|
+
description="Download 2slides pages and voices as ZIP archive (FREE)",
|
|
118
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
119
|
+
epilog="""
|
|
120
|
+
Examples:
|
|
121
|
+
# Download with default filename
|
|
122
|
+
%(prog)s --job-id "abc-123-def-456"
|
|
123
|
+
|
|
124
|
+
# Download to specific path
|
|
125
|
+
%(prog)s --job-id "abc-123-def-456" --output slides.zip
|
|
126
|
+
|
|
127
|
+
Archive Contents:
|
|
128
|
+
- Pages as PNG files
|
|
129
|
+
- Voice files as WAV
|
|
130
|
+
- Transcripts
|
|
131
|
+
|
|
132
|
+
Note: Download URLs are valid for 1 hour only
|
|
133
|
+
Cost: Completely FREE (no credits used)
|
|
134
|
+
"""
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
parser.add_argument("--job-id", required=True, help="Job ID from slide generation")
|
|
138
|
+
parser.add_argument("--output", help="Output ZIP file path (default: <job_id>.zip)")
|
|
139
|
+
|
|
140
|
+
args = parser.parse_args()
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
output_path = download_slides_pages_voices(
|
|
144
|
+
job_id=args.job_id,
|
|
145
|
+
output_path=args.output
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Output path for easy parsing
|
|
149
|
+
print(json.dumps({"success": True, "output": output_path}, indent=2))
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
main()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate AI voice narration for slides using the 2slides API.
|
|
4
|
+
Supports single and multi-speaker modes with 30 voice options.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
import requests
|
|
12
|
+
from typing import Optional, Dict, Any, List
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
API_BASE_URL = "https://2slides.com/api/v1"
|
|
16
|
+
|
|
17
|
+
# Available voice options (30 voices)
|
|
18
|
+
AVAILABLE_VOICES = [
|
|
19
|
+
"Puck", "Aoede", "Charon", "Kore", "Fenrir", "Phoebe", "Asteria",
|
|
20
|
+
"Luna", "Stella", "Theia", "Helios", "Atlas", "Clio", "Melpomene",
|
|
21
|
+
"Calliope", "Erato", "Euterpe", "Polyhymnia", "Terpsichore", "Thalia",
|
|
22
|
+
"Urania", "Zeus", "Hera", "Poseidon", "Athena", "Apollo", "Artemis",
|
|
23
|
+
"Ares", "Aphrodite", "Hephaestus"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_api_key() -> str:
|
|
28
|
+
"""Get API key from environment variable."""
|
|
29
|
+
api_key = os.environ.get("SLIDES_2SLIDES_API_KEY")
|
|
30
|
+
if not api_key:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"API key not found. Set SLIDES_2SLIDES_API_KEY environment variable.\n"
|
|
33
|
+
"Get your API key from: https://2slides.com/api"
|
|
34
|
+
)
|
|
35
|
+
return api_key
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def generate_narration(
|
|
39
|
+
job_id: str,
|
|
40
|
+
language: str = "Auto",
|
|
41
|
+
voice: str = "Puck",
|
|
42
|
+
multi_speaker: bool = False,
|
|
43
|
+
api_key: Optional[str] = None
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Generate AI voice narration for slides.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
job_id: Job ID from slide generation (must be UUID format for Nano Banana)
|
|
50
|
+
language: Language for narration (default: "Auto")
|
|
51
|
+
Options: Auto, English, Simplified Chinese, Traditional Chinese, Spanish,
|
|
52
|
+
Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German,
|
|
53
|
+
Vietnamese, Turkish, Polish, Italian, Korean
|
|
54
|
+
voice: Voice name (default: "Puck")
|
|
55
|
+
Options: Puck, Aoede, Charon, Kore, Fenrir, Phoebe, Asteria, Luna, Stella,
|
|
56
|
+
Theia, Helios, Atlas, Clio, Melpomene, Calliope, Erato, Euterpe, Polyhymnia,
|
|
57
|
+
Terpsichore, Thalia, Urania, Zeus, Hera, Poseidon, Athena, Apollo, Artemis,
|
|
58
|
+
Ares, Aphrodite, Hephaestus
|
|
59
|
+
multi_speaker: Enable multi-speaker mode (default: False)
|
|
60
|
+
api_key: API key (uses env var if not provided)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict with narration generation result
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
- Job must be completed before adding narration
|
|
67
|
+
- Cost: 210 credits per page (10 for text, 200 for audio)
|
|
68
|
+
- Processing time: Varies by slide count
|
|
69
|
+
"""
|
|
70
|
+
if api_key is None:
|
|
71
|
+
api_key = get_api_key()
|
|
72
|
+
|
|
73
|
+
if voice not in AVAILABLE_VOICES:
|
|
74
|
+
print(f"Warning: Voice '{voice}' not in known voices list", file=sys.stderr)
|
|
75
|
+
print(f"Available voices: {', '.join(AVAILABLE_VOICES)}", file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
headers = {
|
|
78
|
+
"Authorization": f"Bearer {api_key}",
|
|
79
|
+
"Content-Type": "application/json"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
payload = {
|
|
83
|
+
"jobId": job_id,
|
|
84
|
+
"language": language,
|
|
85
|
+
"voice": voice,
|
|
86
|
+
"multiSpeaker": multi_speaker
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
url = f"{API_BASE_URL}/slides/generate-narration"
|
|
90
|
+
|
|
91
|
+
print("Generating voice narration...", file=sys.stderr)
|
|
92
|
+
print(f"Voice: {voice}, Multi-speaker: {multi_speaker}", file=sys.stderr)
|
|
93
|
+
|
|
94
|
+
# Set reasonable timeout for narration generation
|
|
95
|
+
timeout = 120
|
|
96
|
+
|
|
97
|
+
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
98
|
+
response.raise_for_status()
|
|
99
|
+
|
|
100
|
+
result = response.json()
|
|
101
|
+
|
|
102
|
+
# Check API response structure
|
|
103
|
+
if not result.get("success"):
|
|
104
|
+
# Common error example:
|
|
105
|
+
# {"error":"Job is not completed","code":"JOB_NOT_COMPLETED",...}
|
|
106
|
+
error_msg = result.get("error", "Unknown error")
|
|
107
|
+
code = result.get("code")
|
|
108
|
+
details = result.get("details")
|
|
109
|
+
extra = f" (code={code})" if code else ""
|
|
110
|
+
raise ValueError(f"API error: {error_msg}{extra}{f' details={details}' if details else ''}")
|
|
111
|
+
|
|
112
|
+
# API may return either:
|
|
113
|
+
# - { success:true, data:{...} }
|
|
114
|
+
# - { success:true, jobId:"...", message:"..." } (no data field)
|
|
115
|
+
data = result.get("data")
|
|
116
|
+
if not data:
|
|
117
|
+
data = {
|
|
118
|
+
"jobId": result.get("jobId") or job_id,
|
|
119
|
+
"status": result.get("status") or "pending",
|
|
120
|
+
"message": result.get("message") or "Narration generation started"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
print("✓ Narration generation started!", file=sys.stderr)
|
|
124
|
+
print(f" Job ID: {data.get('jobId')}", file=sys.stderr)
|
|
125
|
+
print("Use get_job_status.py to check progress", file=sys.stderr)
|
|
126
|
+
|
|
127
|
+
return data
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def list_voices():
|
|
131
|
+
"""Print available voices."""
|
|
132
|
+
print("Available voices (30 total):")
|
|
133
|
+
print("-" * 40)
|
|
134
|
+
for i, voice in enumerate(AVAILABLE_VOICES, 1):
|
|
135
|
+
print(f"{i:2d}. {voice}")
|
|
136
|
+
print("-" * 40)
|
|
137
|
+
print("\nPopular choices: Puck, Aoede, Charon")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main():
|
|
141
|
+
parser = argparse.ArgumentParser(
|
|
142
|
+
description="Generate AI voice narration for 2slides presentations",
|
|
143
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
144
|
+
epilog="""
|
|
145
|
+
Examples:
|
|
146
|
+
# List available voices
|
|
147
|
+
%(prog)s --list-voices
|
|
148
|
+
|
|
149
|
+
# Generate narration with default voice
|
|
150
|
+
%(prog)s --job-id "abc-123-def-456"
|
|
151
|
+
|
|
152
|
+
# Generate with specific voice
|
|
153
|
+
%(prog)s --job-id "abc-123-def-456" --voice "Aoede"
|
|
154
|
+
|
|
155
|
+
# Generate with multi-speaker mode
|
|
156
|
+
%(prog)s --job-id "abc-123-def-456" --multi-speaker
|
|
157
|
+
|
|
158
|
+
# Generate in Spanish
|
|
159
|
+
%(prog)s --job-id "abc-123-def-456" --language "Spanish" --voice "Charon"
|
|
160
|
+
|
|
161
|
+
Credit Cost: 210 credits per page (10 for text, 200 for audio)
|
|
162
|
+
"""
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
parser.add_argument("--job-id", help="Job ID from slide generation (UUID format)")
|
|
166
|
+
parser.add_argument("--language", default="Auto", help="Narration language (default: Auto)")
|
|
167
|
+
parser.add_argument("--voice", default="Puck", help="Voice name (default: Puck)")
|
|
168
|
+
parser.add_argument("--multi-speaker", action="store_true", help="Enable multi-speaker mode")
|
|
169
|
+
parser.add_argument("--list-voices", action="store_true", help="List available voices and exit")
|
|
170
|
+
|
|
171
|
+
args = parser.parse_args()
|
|
172
|
+
|
|
173
|
+
if args.list_voices:
|
|
174
|
+
list_voices()
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
if not args.job_id:
|
|
178
|
+
print("Error: --job-id is required (or use --list-voices)", file=sys.stderr)
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
result = generate_narration(
|
|
183
|
+
job_id=args.job_id,
|
|
184
|
+
language=args.language,
|
|
185
|
+
voice=args.voice,
|
|
186
|
+
multi_speaker=args.multi_speaker
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
print(json.dumps(result, indent=2))
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
main()
|