Initial commit: add all skills files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 16:52:49 +08:00
commit 6487becf60
396 changed files with 108871 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
MiniMax Text-to-Image — synchronous generation.
Usage:
python minimax_image.py "A cat in space" -o cat.png
python minimax_image.py "Mountain landscape" -o bg.png --ratio 16:9
python minimax_image.py "Product icons" -o icons.png -n 4 --ratio 1:1
Env: MINIMAX_API_KEY (required)
"""
import os
import sys
import json
import argparse
import requests
API_KEY = os.getenv("MINIMAX_API_KEY")
API_BASE = "https://api.minimax.io/v1"
ASPECT_RATIOS = ["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]
def _headers():
if not API_KEY:
raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'")
return {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def generate_image(
prompt: str,
model: str = "image-01",
aspect_ratio: str = "1:1",
n: int = 1,
response_format: str = "url",
prompt_optimizer: bool = False,
seed: int = None,
) -> dict:
"""Generate image(s). Returns API response dict."""
payload = {
"model": model,
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"n": n,
"response_format": response_format,
"prompt_optimizer": prompt_optimizer,
}
if seed is not None:
payload["seed"] = seed
resp = requests.post(
f"{API_BASE}/image_generation",
headers=_headers(),
json=payload,
timeout=120,
)
resp.raise_for_status()
data = resp.json()
base_resp = data.get("base_resp", {})
if base_resp.get("status_code", 0) != 0:
raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}")
return data
def download_and_save(url: str, output_path: str):
"""Download image from URL and save."""
resp = requests.get(url, timeout=60)
resp.raise_for_status()
with open(output_path, "wb") as f:
f.write(resp.content)
return len(resp.content)
def main():
p = argparse.ArgumentParser(description="MiniMax Text-to-Image")
p.add_argument("prompt", help="Image description (max 1500 chars)")
p.add_argument("-o", "--output", required=True, help="Output file path (.png/.jpg)")
p.add_argument("--model", default="image-01", help="Model (default: image-01)")
p.add_argument("--ratio", default="1:1", choices=ASPECT_RATIOS, help="Aspect ratio (default: 1:1)")
p.add_argument("-n", "--count", type=int, default=1, choices=range(1, 10), help="Number of images (1-9, default: 1)")
p.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
p.add_argument("--optimize", action="store_true", help="Enable prompt auto-optimization")
p.add_argument("--base64", action="store_true", help="Use base64 response instead of URL")
args = p.parse_args()
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
fmt = "base64" if args.base64 else "url"
result = generate_image(
prompt=args.prompt,
model=args.model,
aspect_ratio=args.ratio,
n=args.count,
response_format=fmt,
prompt_optimizer=args.optimize,
seed=args.seed,
)
meta = result.get("metadata", {})
print(f"Generated: {meta.get('success_count', '?')} success, {meta.get('failed_count', '?')} failed")
if args.base64:
images = result.get("data", {}).get("image_base64", [])
import base64
for i, b64 in enumerate(images):
path = args.output if len(images) == 1 else _numbered_path(args.output, i)
raw = base64.b64decode(b64)
with open(path, "wb") as f:
f.write(raw)
print(f"OK: {len(raw)} bytes -> {path}")
else:
urls = result.get("data", {}).get("image_urls", [])
for i, url in enumerate(urls):
path = args.output if len(urls) == 1 else _numbered_path(args.output, i)
size = download_and_save(url, path)
print(f"OK: {size} bytes -> {path}")
def _numbered_path(path: str, index: int) -> str:
"""Insert index before extension: out.png -> out-0.png"""
base, ext = os.path.splitext(path)
return f"{base}-{index}{ext}"
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
MiniMax Music Generation (HTTP)
Self-contained: no external dependencies beyond `requests`.
Usage:
python minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker" -o song.mp3
python minimax_music.py --prompt "Upbeat pop, energetic" --auto-lyrics -o pop.mp3
python minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3
Env: MINIMAX_API_KEY (required)
"""
import os
import sys
import json
import argparse
import requests
API_KEY = os.getenv("MINIMAX_API_KEY")
API_BASE = os.getenv("MINIMAX_API_BASE", "https://api.minimax.io/v1")
def generate_music(
prompt: str = "",
lyrics: str = "",
model: str = "music-2.5+",
is_instrumental: bool = False,
lyrics_optimizer: bool = False,
sample_rate: int = 44100,
bitrate: int = 256000,
fmt: str = "mp3",
output_format: str = "hex",
timeout: int = 600,
) -> dict:
"""Synchronous HTTP music generation. Returns dict with audio bytes and metadata."""
if not API_KEY:
raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'")
payload = {
"model": model,
"audio_setting": {
"sample_rate": sample_rate,
"bitrate": bitrate,
"format": fmt,
},
"output_format": output_format,
}
if prompt:
payload["prompt"] = prompt
if lyrics:
payload["lyrics"] = lyrics
if is_instrumental:
payload["is_instrumental"] = True
if lyrics_optimizer:
payload["lyrics_optimizer"] = True
resp = requests.post(
f"{API_BASE}/music_generation",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json=payload,
timeout=timeout,
)
resp.raise_for_status()
data = resp.json()
# Check API-level error
base_resp = data.get("base_resp", {})
if base_resp.get("status_code", 0) != 0:
raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}")
status = data.get("data", {}).get("status")
if status != 2:
raise SystemExit(f"Generation incomplete (status={status}): {json.dumps(data, indent=2)}")
audio_data = data.get("data", {}).get("audio", "")
if not audio_data:
raise SystemExit(f"No audio in response: {json.dumps(data, indent=2)}")
extra = data.get("extra_info", {})
if output_format == "hex":
audio_bytes = bytes.fromhex(audio_data)
else:
# URL mode — audio_data is a URL string
audio_bytes = None
return {
"audio_bytes": audio_bytes,
"audio_url": audio_data if output_format == "url" else None,
"duration": extra.get("music_duration"),
"sample_rate": extra.get("music_sample_rate"),
"channels": extra.get("music_channel"),
"bitrate": extra.get("bitrate"),
"size": extra.get("music_size"),
}
def main():
p = argparse.ArgumentParser(description="MiniMax Music Generation (HTTP)")
p.add_argument("-o", "--output", required=True, help="Output file path")
p.add_argument("--prompt", default="", help="Music description: style, mood, scenario (max 2000 chars)")
p.add_argument("--lyrics", default="", help="Song lyrics with structure tags (max 3500 chars)")
p.add_argument("--lyrics-file", default="", help="Read lyrics from file instead of --lyrics")
p.add_argument("--model", default="music-2.5+", choices=["music-2.5+", "music-2.5"], help="Model (default: music-2.5+)")
p.add_argument("--instrumental", action="store_true", help="Generate instrumental only (no vocals)")
p.add_argument("--auto-lyrics", action="store_true", help="Auto-generate lyrics from prompt")
p.add_argument("--format", default="mp3", dest="fmt", choices=["mp3", "wav", "pcm"], help="Audio format (default: mp3)")
p.add_argument("--sample-rate", type=int, default=44100, choices=[16000, 24000, 32000, 44100], help="Sample rate (default: 44100)")
p.add_argument("--bitrate", type=int, default=256000, choices=[32000, 64000, 128000, 256000], help="Bitrate (default: 256000)")
args = p.parse_args()
lyrics = args.lyrics
if args.lyrics_file:
with open(args.lyrics_file, "r") as f:
lyrics = f.read()
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
result = generate_music(
prompt=args.prompt,
lyrics=lyrics,
model=args.model,
is_instrumental=args.instrumental,
lyrics_optimizer=args.auto_lyrics,
sample_rate=args.sample_rate,
bitrate=args.bitrate,
fmt=args.fmt,
)
if result["audio_bytes"]:
with open(args.output, "wb") as f:
f.write(result["audio_bytes"])
size = len(result["audio_bytes"])
else:
# URL mode — download
r = requests.get(result["audio_url"], timeout=120)
r.raise_for_status()
with open(args.output, "wb") as f:
f.write(r.content)
size = len(r.content)
duration = result.get("duration", "?")
print(f"OK: {size} bytes -> {args.output} (duration: {duration}s)")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
MiniMax Sync TTS (HTTP)
Self-contained: no external dependencies beyond `requests`.
Usage:
python minimax_tts.py "Hello world" -o output.mp3
python minimax_tts.py "你好世界" -o hi.mp3 -v female-shaonv --model speech-2.8-hd
python minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --speed 0.8 --format wav
Env: MINIMAX_API_KEY (required)
"""
import os
import sys
import json
import argparse
import requests
API_KEY = os.getenv("MINIMAX_API_KEY")
API_BASE = os.getenv("MINIMAX_API_BASE", "https://api.minimax.io/v1")
def tts(
text: str,
voice_id: str = "male-qn-qingse",
model: str = "speech-2.8-hd",
speed: float = 1.0,
volume: float = 1.0,
pitch: int = 0,
emotion: str = "",
sample_rate: int = 32000,
bitrate: int = 128000,
fmt: str = "mp3",
language_boost: str = "auto",
timeout: int = 120,
) -> bytes:
"""Synchronous HTTP TTS. Returns raw audio bytes."""
if not API_KEY:
raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'")
voice_setting = {"voice_id": voice_id, "speed": speed, "vol": volume, "pitch": pitch}
if emotion:
voice_setting["emotion"] = emotion
payload = {
"model": model,
"text": text,
"stream": False,
"voice_setting": voice_setting,
"audio_setting": {
"sample_rate": sample_rate,
"bitrate": bitrate,
"format": fmt,
"channel": 1,
},
"language_boost": language_boost,
"output_format": "hex",
}
resp = requests.post(
f"{API_BASE}/t2a_v2",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json=payload,
timeout=timeout,
)
resp.raise_for_status()
data = resp.json()
# Check API-level error
base_resp = data.get("base_resp", {})
if base_resp.get("status_code", 0) != 0:
raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}")
audio_hex = data.get("data", {}).get("audio", "")
if not audio_hex:
raise SystemExit(f"No audio in response: {json.dumps(data, indent=2)}")
return bytes.fromhex(audio_hex)
def main():
p = argparse.ArgumentParser(description="MiniMax Sync TTS (HTTP)")
p.add_argument("text", help="Text to synthesize (max 10000 chars)")
p.add_argument("-o", "--output", required=True, help="Output file path")
p.add_argument("-v", "--voice", default="male-qn-qingse", help="Voice ID")
p.add_argument("--model", default="speech-2.8-hd", help="Model (default: speech-2.8-hd)")
p.add_argument("--speed", type=float, default=1.0, help="Speed 0.5-2.0")
p.add_argument("--volume", type=float, default=1.0, help="Volume 0.1-10")
p.add_argument("--pitch", type=int, default=0, help="Pitch -12 to 12")
p.add_argument("--emotion", default="", help="Emotion tag (happy/sad/angry/...)")
p.add_argument("--format", default="mp3", dest="fmt", help="Audio format (mp3/wav/flac)")
p.add_argument("--sample-rate", type=int, default=32000, help="Sample rate")
p.add_argument("--lang", default="auto", help="Language boost")
args = p.parse_args()
os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True)
audio = tts(
text=args.text,
voice_id=args.voice,
model=args.model,
speed=args.speed,
volume=args.volume,
pitch=args.pitch,
emotion=args.emotion,
fmt=args.fmt,
sample_rate=args.sample_rate,
language_boost=args.lang,
)
with open(args.output, "wb") as f:
f.write(audio)
print(f"OK: {len(audio)} bytes -> {args.output}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
MiniMax Text-to-Video — async generation with polling and download.
Usage:
python minimax_video.py "A cat playing piano" -o cat.mp4
python minimax_video.py "Ocean waves [Truck left]" -o waves.mp4 --model MiniMax-Hailuo-2.3 --duration 10
python minimax_video.py "City skyline at sunset [Push in]" -o city.mp4 --resolution 1080P
Env: MINIMAX_API_KEY (required)
"""
import os
import sys
import json
import time
import argparse
import requests
API_KEY = os.getenv("MINIMAX_API_KEY")
API_BASE = "https://api.minimax.io/v1"
def _headers():
if not API_KEY:
raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'")
return {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def _check_resp(data):
base_resp = data.get("base_resp", {})
code = base_resp.get("status_code", 0)
if code != 0:
msg = base_resp.get("status_msg", "Unknown error")
raise SystemExit(f"API Error [{code}]: {msg}")
def create_task(
prompt: str,
model: str = "MiniMax-Hailuo-2.3",
duration: int = 6,
resolution: str = "768P",
prompt_optimizer: bool = True,
) -> str:
"""Submit a video generation task. Returns task_id."""
payload = {
"model": model,
"prompt": prompt,
"duration": duration,
"resolution": resolution,
"prompt_optimizer": prompt_optimizer,
}
resp = requests.post(
f"{API_BASE}/video_generation",
headers=_headers(),
json=payload,
timeout=30,
)
resp.raise_for_status()
data = resp.json()
_check_resp(data)
task_id = data.get("task_id")
if not task_id:
raise SystemExit(f"No task_id in response: {json.dumps(data, indent=2)}")
return task_id
def poll_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str:
"""Poll task status until Success. Returns file_id."""
elapsed = 0
while elapsed < max_wait:
resp = requests.get(
f"{API_BASE}/query/video_generation",
headers=_headers(),
params={"task_id": task_id},
timeout=30,
)
resp.raise_for_status()
data = resp.json()
_check_resp(data)
status = data.get("status", "")
file_id = data.get("file_id", "")
if status == "Success":
if not file_id:
raise SystemExit("Task succeeded but no file_id returned")
print(f" Done! file_id={file_id}")
return file_id
elif status == "Fail":
raise SystemExit(f"Video generation failed: {json.dumps(data, indent=2)}")
else:
print(f" [{elapsed}s] Status: {status}...")
time.sleep(interval)
elapsed += interval
raise SystemExit(f"Timeout after {max_wait}s. task_id={task_id}, check manually.")
def download_video(file_id: str, output_path: str):
"""Retrieve download URL via file_id and save the video."""
resp = requests.get(
f"{API_BASE}/files/retrieve",
headers=_headers(),
params={"file_id": file_id},
timeout=30,
)
resp.raise_for_status()
data = resp.json()
_check_resp(data)
download_url = data.get("file", {}).get("download_url", "")
if not download_url:
raise SystemExit(f"No download_url in response: {json.dumps(data, indent=2)}")
print(f" Downloading from {download_url[:80]}...")
video_resp = requests.get(download_url, timeout=300)
video_resp.raise_for_status()
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
with open(output_path, "wb") as f:
f.write(video_resp.content)
print(f"OK: {len(video_resp.content)} bytes -> {output_path}")
def generate(
prompt: str,
output_path: str,
model: str = "MiniMax-Hailuo-2.3",
duration: int = 6,
resolution: str = "768P",
prompt_optimizer: bool = True,
poll_interval: int = 10,
max_wait: int = 600,
):
"""Full pipeline: create task -> poll -> download."""
print(f"Creating video task...")
print(f" Model: {model} | Duration: {duration}s | Resolution: {resolution}")
print(f" Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
task_id = create_task(prompt, model, duration, resolution, prompt_optimizer)
print(f" task_id={task_id}")
print(f"Waiting for generation...")
file_id = poll_task(task_id, poll_interval, max_wait)
download_video(file_id, output_path)
def main():
p = argparse.ArgumentParser(description="MiniMax Text-to-Video")
p.add_argument("prompt", help="Video description (max 2000 chars). Use [Camera Command] for camera control.")
p.add_argument("-o", "--output", required=True, help="Output file path (.mp4)")
p.add_argument("--model", default="MiniMax-Hailuo-2.3",
choices=["MiniMax-Hailuo-2.3", "MiniMax-Hailuo-02", "T2V-01-Director", "T2V-01"],
help="Model (default: MiniMax-Hailuo-2.3)")
p.add_argument("--duration", type=int, default=6, choices=[6, 10], help="Duration in seconds (default: 6)")
p.add_argument("--resolution", default="768P", choices=["720P", "768P", "1080P"], help="Resolution (default: 768P)")
p.add_argument("--no-optimize", action="store_true", help="Disable prompt auto-optimization")
p.add_argument("--poll-interval", type=int, default=10, help="Poll interval in seconds (default: 10)")
p.add_argument("--max-wait", type=int, default=600, help="Max wait time in seconds (default: 600)")
args = p.parse_args()
generate(
prompt=args.prompt,
output_path=args.output,
model=args.model,
duration=args.duration,
resolution=args.resolution,
prompt_optimizer=not args.no_optimize,
poll_interval=args.poll_interval,
max_wait=args.max_wait,
)
if __name__ == "__main__":
main()