Skill: irron/qwenz-image-gen (version 1.0.1)

--- name: qwen-image description: Generate images using Alibaba Cloud Bailian Qwen-Image and Z-Image models (通义千图文生图 + 人像照片模型) homepage: https://help.aliyun.com/zh/model-studio/qwen-image-api metadata: openclaw: emoji: 🎨 requires: env: - DASHSCOPE_API_KEY --- # Qwen-Image / Z-Image Skill 基于阿里云百炼的文图生成技能，**智能识别场景**自动选择最佳模型： - 🧍 **人像/照片** → **z-image-turbo**（专精高质量人像，支持胶片质感） - 🖼️ **通用文图** → **qwen-image-max**（擅长复杂文字渲染、艺术风格） ## Features - **🤖 智能模型选择**：自动检测人像场景，无需手动选择模型 - **🎨 双模型支持**：Qwen-Image（通用） + Z-Image（人像） - **✍️ 中文文字渲染**：在图像中完美呈现复杂中文字符 - **📐 多尺寸支持**：16:9、4:3、1:1、3:4、9:16 等多种比例 - **🔧 可手动指定**：支持强制使用特定模型 ## Setup 1. 从 [阿里云百炼控制台](https://bailian.console.aliyun.com/) 获取 API Key 2. 配置方式选择其一： - **环境变量**：`export DASHSCOPE_API_KEY="sk-xxx"` - **TOOLS.md**：在 TOOLS.md 中添加 `DASHSCOPE_API_KEY: sk-xxx` > **地域注意**：北京和新加坡地域 API Key 不互通 ## Available Models | 模型 | 描述 | 最佳场景 | |------|------|----------| | **qwen-image-max** | 最佳质量，减少AI痕迹，文字渲染优秀 | 漫画、插画、图文设计、风景、静物 | | **qwen-image-plus** | 质量与速度平衡 | 通用场景 | | **qwen-image** | 基础模型 | 快速生成 | | **z-image-turbo** | 人像专精，胶片质感，真实感强 | 人像照片、人像写真、film grain效果 | ## Supported Sizes | 尺寸 | 比例 | 说明 | |------|------|------| | 1664*928 | 16:9 | 横向宽屏（通用默认）| | 1472*1104 | 4:3 | 标准比例 | | 1328*1328 | 1:1 | 方形 | | 1104*1472 | 3:4 | 竖向 | | 928*1664 | 9:16 | 手机竖屏 | | 1120*1440 | 4:5 | **人像推荐** | ## Usage ### 🎯 快速入门（推荐） 直接写提示词，**自动识别场景**并选择最佳模型： ```bash # 人像类 → 自动使用 z-image-turbo python scripts/generate.py "短发少女，清新自然风格，微笑" # 通用类 → 自动使用 qwen-image-max python scripts/generate.py "七龙珠孙悟空对战比克大魔王，漫画风格" # 含film grain关键词 → 自动使用 z-image python scripts/generate.py "胶片感，Kodak Portra 400风格的人像" ``` ### 🔧 高级选项 ```bash python scripts/generate.py "prompt" \ --model z \ # 强制指定模型 (z/qwen/auto) --size 1328*1328 \ # 图片尺寸 --prompt-extend \ # 开启提示词扩展 --no-watermark \ # 禁用水印 --output my-image.png # 输出路径 ``` ## Auto-Detection Keywords 以下关键词会触发自动选择 **z-image-turbo**： **人物类**：人、女、男、少女、帅哥、美女、肖像、人物、face、facial **照片/胶片类**：photo、photograph、film grain、analog、Kodak、胶片、portra、cinematic、photorealistic、真实、写真人像 ## 使用示例 ### 人像照片（z-image） ```bash python scripts/generate.py "东亚年轻女性，户外雪地场景，film grain效果，胶片质感" ``` ### 漫画风格（qwen-image） ```bash python scripts/generate.py "七龙珠孙悟空对战比克大魔王，漫画风格，能量波爆炸，天空背景" ``` ### 带中文文字的漫画 ```bash python scripts/generate.py "一副对联，上联：智启千问，下联：机道为善，横批：人机共生" ``` ## Tips - **人像首选 z-image**：对面部细节、皮肤质感、胶片感优化更好 - **文字渲染选 qwen**：复杂中文、图文混排场景更精准 - **自动模式省心**：无需纠结选哪个模型 - **提示词长度**：正向 ≤800字符，负向 ≤500字符 - **扩展提示词**：`--prompt-extend` 可以让AI自动优化你的描述 --- *Qwen-Image Skill - 国产文生图利器 🇨🇳*

path: scripts/generate.py
content: #!/usr/bin/env python3
"""
Qwen-Image / Z-Image 生图脚本
自动判断场景选择模型，人像用 z-image，其他用 qwen-image
"""

import argparse
import os
import sys
import json
import re
from pathlib import Path
import http.client
from urllib.parse import urlparse
from datetime import datetime


def get_api_key() -> str:
    """从环境变量或 TOOLS.md 获取 API Key"""
    # 1. 环境变量
    key = os.environ.get("DASHSCOPE_API_KEY")
    if key:
        return key
    
    # 2. 从 TOOLS.md 读取
    possible_paths = [
        Path(__file__).parent.parent.parent.parent / "TOOLS.md",
        Path.cwd() / "TOOLS.md",
        Path("/home/admin/clawd/TOOLS.md"),
    ]
    
    for path in possible_paths:
        try:
            if path.exists():
                content = path.read_text(encoding="utf-8")
                match = re.search(r'DASHSCOPE_API_KEY:\s*(\S+)', content)
                if match:
                    key = match.group(1)
                    if key and not key.startswith('请在这里'):
                        return key
        except Exception:
            continue
    
    return None


# 人像/照片关键词
PORTRAIT_KEYWORDS = [
    "人", "女", "男", "少女", "帅哥", "美女", "肖像", "portrait", "woman", "man", "girl", "boy",
    "人物", "face", " facial", "selfie", "photo", "photograph", "film grain", "analog",
    "Kodak", "胶片", "portra", "cinematic", "photorealistic", "真实", "写真人像"
]

def is_portrait_prompt(prompt: str) -> bool:
    """判断提示词是否涉及人像/照片场景"""
    prompt_lower = prompt.lower()
    return any(kw in prompt or kw.lower() in prompt_lower for kw in PORTRAIT_KEYWORDS)


# 模型配置
MODELS = {
    "qwen": {
        "name": "qwen-image-max",
        "sizes": {
            "1664*928": (1664, 928),   # 16:9
            "1472*1104": (1472, 1104), # 4:3
            "1328*1328": (1328, 1328), # 1:1
            "1104*1472": (1104, 1472), # 3:4
            "928*1664": (928, 1664),   # 9:16
        },
        "default_size": "1328*1328"
    },
    "z": {
        "name": "z-image-turbo",
        "sizes": {
            "1120*1440": (1120, 1440), # 人像推荐
            "1664*928": (1664, 928),
            "1328*1328": (1328, 1328),
        },
        "default_size": "1120*1440"
    }
}


def parse_size(size_str: str) -> tuple:
    """解析尺寸字符串，返回 (width, height)"""
    parts = size_str.split("*")
    if len(parts) == 2:
        return int(parts[0]), int(parts[1])
    return 1328, 1328


def generate_image(
    prompt: str,
    model_type: str = "auto",
    size: str = None,
    prompt_extend: int = 0,
    watermark: bool = False,
    output: str = None
) -> str:
    """
    生成图片
    
    Args:
        prompt: 提示词
        model_type: auto/qwen/z | auto 自动判断
        size: 尺寸
        prompt_extend: 是否扩展提示词 (0/1)
        watermark: 是否添加水印
        output: 输出路径
    """
    [REDACTED_SECRET])
    if not [REDACTED_SECRET]"❌ 错误: 未设置 DASHSCOPE_API_KEY 环境变量")
        print("请设置: export DASHSCOPE_API_KEY='your-api-key'")
        sys.exit(1)
    
    # 判断模型
    if model_type == "auto":
        if is_portrait_prompt(prompt):
            model_config = MODELS["z"]
            model_type = "z"
            print("🔍 检测到人像/照片场景，自动使用 z-image 模型")
        else:
            model_config = MODELS["qwen"]
            model_type = "qwen"
    else:
        model_config = MODELS.get(model_type, MODELS["qwen"])
    
    # 确定尺寸
    if size and size in model_config["sizes"]:
        size_str = size
    else:
        size_str = model_config["default_size"]
    width, height = model_config["sizes"][size_str]
    
    # 构建请求体 (messages 格式)
    payload = {
        "model": model_config["name"],
        "input": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": prompt
                        }
                    ]
                }
            ]
        },
        "parameters": {
            "size": size_str,
            "prompt_extend": bool(prompt_extend),
            "watermark": watermark
        }
    }
    
    # 人像类添加额外参数
    if model_type == "z" and "film grain" not in prompt.lower():
        # 自动添加胶片效果，除非用户已指定
        pass  # 可选：可以自动增强提示词
    
    print(f"🎨 正在生成图片...")
    print(f"   模型: {model_config['name']}")
    print(f"   尺寸: {size_str} ({width}x{height})")
    print(f"   提示词: {prompt[:60]}{'...' if len(prompt) > 60 else ''}")
    
    # 发送请求
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    try:
        conn = http.client.HTTPSConnection("dashscope.aliyuncs.com", timeout=120)
        conn.request(
            "POST",
            "/api/v1/services/aigc/multimodal-generation/generation",
            body=json.dumps(payload),
            headers=headers
        )
        
        response = conn.getresponse()
        data = response.read().decode("utf-8")
        
        if response.status != 200:
            print(f"❌ HTTP 错误 {response.status}: {data}")
            sys.exit(1)
        
        result = json.loads(data)
        
        # 解析响应: output.choices[0].message.content[0].image
        if "output" in result and "choices" in result["output"]:
            choice = result["output"]["choices"][0]
            content = choice["message"]["content"]
            
            # 找到 image
            image_url = None
            for item in content:
                if "image" in item:
                    image_url = item["image"]
                    break
            
            if not image_url:
                print(f"❌ 未找到图片 URL: {result}")
                sys.exit(1)
            
            # 确定输出文件名
            if not output:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                output = f"{model_type}_image_{timestamp}.png"
            
            # 下载图片
            print(f"⬇️  下载图片...")
            parsed = urlparse(image_url)
            img_conn = http.client.HTTPSConnection(parsed.netloc, timeout=60)
            img_conn.request("GET", parsed.path + (f"?{parsed.query}" if parsed.query else ""))
            img_resp = img_conn.getresponse()
            
            with open(output, "wb") as f:
                f.write(img_resp.read())
            img_conn.close()
            
            print(f"✅ 图片已保存: {os.path.abspath(output)}")
            conn.close()
            return output
        
        print(f"❌ API 返回异常: {result}")
        sys.exit(1)
        
    except Exception as e:
        print(f"❌ 错误: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(
        description="使用 Qwen-Image / Z-Image 模型生成图片",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  # 自动判断模型
  python generate.py "一只可爱的橘猫"
  
  # 指定人像模式 (z-image)
  python generate.py "短发少女" --model z --size 1120*1440
  
  # 通用模式 (qwen-image)
  python generate.py "赛博朋克城市" --model qwen --size 1664*928
        """
    )
    
    parser.add_argument("prompt", help="图片生成提示词（支持中文）")
    parser.add_argument("--model", default="auto", choices=["auto", "qwen", "z"],
                        help="模型选择 (auto/qwen/z, 默认 auto)")
    parser.add_argument("--size", help="图片尺寸 (如: 1664*928, 1120*1440)")
    parser.add_argument("--extend", action="store_true",
                        help="开启提示词扩展")
    parser.add_argument("--watermark", action="store_true",
                        help="添加水印")
    parser.add_argument("--output", "-o", help="输出文件路径")
    
    args = parser.parse_args()
    
    generate_image(
        prompt=args.prompt,
        model_type=args.model,
        size=args.size,
        prompt_extend=args.extend,
        watermark=args.watermark,
        output=args.output
    )


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)