205 lines
6.5 KiB
Python
205 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
||
# /// script
|
||
# requires-python = ">=3.10"
|
||
# dependencies = [
|
||
# "requests>=2.31.0",
|
||
# ]
|
||
# ///
|
||
"""
|
||
Generate images using Qwen Image API (Alibaba Cloud DashScope).
|
||
|
||
Usage:
|
||
uv run generate_image.py --prompt "your image description" --filename "output.png" [--model qwen-image-max|qwen-image-turbo] [--size 1664*928|1024*1024|720*1280|1280*720] [--api-key KEY]
|
||
"""
|
||
|
||
import argparse
|
||
import os
|
||
import sys
|
||
import json
|
||
import base64
|
||
from pathlib import Path
|
||
|
||
|
||
def get_api_key(provided_key: str | None) -> str | None:
|
||
"""Get API key from argument first, then environment."""
|
||
if provided_key:
|
||
return provided_key
|
||
return os.environ.get("DASHSCOPE_API_KEY")
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="Generate images using Qwen Image API"
|
||
)
|
||
parser.add_argument(
|
||
"--prompt", "-p",
|
||
required=True,
|
||
help="Image description/prompt"
|
||
)
|
||
parser.add_argument(
|
||
"--filename", "-f",
|
||
help="Output filename (optional, if not provided will only return URL)"
|
||
)
|
||
parser.add_argument(
|
||
"--model", "-m",
|
||
choices=["qwen-image-max", "qwen-image-turbo", "qwen-image-plus-2026-01-09"],
|
||
default="qwen-image-max",
|
||
help="Model to use: qwen-image-max (default) or qwen-image-turbo"
|
||
)
|
||
parser.add_argument(
|
||
"--size", "-s",
|
||
choices=["1664*928", "1024*1024", "720*1280", "1280*720"],
|
||
default="1664*928",
|
||
help="Output size (default: 1664*928 for 16:9 ratio)"
|
||
)
|
||
parser.add_argument(
|
||
"--negative-prompt", "-n",
|
||
default="低分辨率,低画质,肢体畸形,手指畸形,画面过饱和,蜡像感,人脸无细节,过度光滑,画面具有AI感。构图混乱。文字模糊,扭曲。",
|
||
help="Negative prompt to avoid unwanted elements"
|
||
)
|
||
parser.add_argument(
|
||
"--no-prompt-extend",
|
||
action="store_true",
|
||
help="Disable automatic prompt enhancement"
|
||
)
|
||
parser.add_argument(
|
||
"--watermark",
|
||
action="store_true",
|
||
help="Add watermark to generated image"
|
||
)
|
||
parser.add_argument(
|
||
"--api-key", "-k",
|
||
help="DashScope API key (overrides DASHSCOPE_API_KEY env var)"
|
||
)
|
||
parser.add_argument(
|
||
"--no-verify-ssl",
|
||
action="store_true",
|
||
help="Disable SSL certificate verification (use with caution)"
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
# Get API key
|
||
api_key = get_api_key(args.api_key)
|
||
if not api_key:
|
||
print("Error: No API key provided.", file=sys.stderr)
|
||
print("Please either:", file=sys.stderr)
|
||
print(" 1. Provide --api-key argument", file=sys.stderr)
|
||
print(" 2. Set DASHSCOPE_API_KEY environment variable", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# Import here after checking API key
|
||
import requests
|
||
|
||
# Set up output path
|
||
|
||
# Build request payload
|
||
payload = {
|
||
"model": args.model,
|
||
"input": {
|
||
"messages": [
|
||
{
|
||
"role": "user",
|
||
"content": [
|
||
{
|
||
"text": args.prompt
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"parameters": {
|
||
"negative_prompt": args.negative_prompt,
|
||
"prompt_extend": not args.no_prompt_extend,
|
||
"watermark": args.watermark,
|
||
"size": args.size
|
||
}
|
||
}
|
||
|
||
print(f"Generating image with {args.model}...")
|
||
print(f"Size: {args.size}")
|
||
print(f"Prompt: {args.prompt}")
|
||
|
||
try:
|
||
# Make API request
|
||
response = requests.post(
|
||
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
|
||
headers={
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {api_key}"
|
||
},
|
||
json=payload,
|
||
timeout=120,
|
||
verify=True
|
||
)
|
||
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
|
||
# Check for errors
|
||
if result.get("code"):
|
||
error_msg = result.get("message", "Unknown error")
|
||
print(f"API Error: {error_msg}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# Extract image URL from response
|
||
output_data = result.get("output", {})
|
||
choices = output_data.get("choices", [])
|
||
|
||
if not choices:
|
||
print("Error: No choices in response", file=sys.stderr)
|
||
print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# Get image URL from first choice
|
||
message = choices[0].get("message", {})
|
||
content = message.get("content", [])
|
||
|
||
if not content or not content[0].get("image"):
|
||
print("Error: No image URL in response", file=sys.stderr)
|
||
print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
image_url = content[0]["image"]
|
||
print(f"\nImage URL: {image_url}")
|
||
|
||
# If filename is provided, download and save the image
|
||
if args.filename:
|
||
output_path = Path(args.filename)
|
||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
print("Downloading image...")
|
||
img_response = requests.get(image_url, timeout=30, verify=not args.no_verify_ssl)
|
||
img_response.raise_for_status()
|
||
|
||
# Save the image
|
||
with open(output_path, "wb") as f:
|
||
f.write(img_response.content)
|
||
|
||
full_path = output_path.resolve()
|
||
print(f"Image saved: {full_path}")
|
||
# Clawdbot parses MEDIA tokens and will attach the file on supported providers.
|
||
print(f"MEDIA: {full_path}")
|
||
else:
|
||
# Just return the URL for Clawdbot to display
|
||
print(f"MEDIA_URL: {image_url}")
|
||
|
||
except requests.exceptions.HTTPError as e:
|
||
print(f"HTTP Error: {e}", file=sys.stderr)
|
||
try:
|
||
error_detail = response.json()
|
||
print(f"Error details: {json.dumps(error_detail, indent=2, ensure_ascii=False)}", file=sys.stderr)
|
||
except:
|
||
print(f"Response text: {response.text}", file=sys.stderr)
|
||
sys.exit(1)
|
||
except requests.exceptions.RequestException as e:
|
||
print(f"Error making API request: {e}", file=sys.stderr)
|
||
sys.exit(1)
|
||
except Exception as e:
|
||
print(f"Error generating image: {e}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|