diff --git a/scripts/ai_gen.py b/scripts/ai_gen.py new file mode 100644 index 00000000..e48c7929 --- /dev/null +++ b/scripts/ai_gen.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +""" +AI 图片/视频生成工具 +支持平台: 通义万相(免费额度)、海螺AI、MiniMax API +用法: + python3 ai_gen.py image "一只橘猫在沙发上" [--model wax3] + python3 ai_gen.py video "海浪拍打沙滩" [--model minimax-video-01] + python3 ai_gen.py status +""" + +import os, sys, json, time, argparse, subprocess, urllib.parse, urllib.request +from pathlib import Path + +# ─── 配置区 ────────────────────────────────────────── +# 通义万相(阿里云百炼)- 有免费额度 +# 获取: https://bailian.console.aliyun.com/ +DASHSCOPE_API_KEY = os.environ.get("DASHSCOPE_API_KEY", "") +# 体验地址: https://tongyi.aliyun.com/wanxiang/ + +# 海螺AI (MiniMax) - API +# 获取: https://www.minimax.io/ 或 https://platform.minimaxi.com/ +MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "") +MINIMAX_GROUP_ID = os.environ.get("MINIMAX_GROUP_ID", "") + +# ─── 工具函数 ───────────────────────────────────────── + +def run(cmd): + r = subprocess.run(cmd, shell=True, capture_output=True, text=True) + return r.stdout.strip(), r.stderr.strip(), r.returncode + +def save_image(url, name=None): + if not name: + name = f"/tmp/ai_gen_{int(time.time())}.png" + subprocess.run(f"curl -sL '{url}' -o '{name}'", shell=True) + print(f" 📷 已保存: {name}") + return name + +def save_video(url, name=None): + if not name: + name = f"/tmp/ai_gen_{int(time.time())}.mp4" + subprocess.run(f"curl -sL '{url}' -o '{name}'", shell=True) + print(f" 🎬 已保存: {name}") + return name + +# ─── 通义万相 ────────────────────────────────────────── +import requests + +def wanxiang_image(prompt, model="wanx3.1-t2i-turbo"): + """通义万相 - 图像生成(免费额度)""" + if not DASHSCOPE_API_KEY: + print(" ⚠️ 未设置 DASHSCOPE_API_KEY") + print(" 体验地址: https://tongyi.aliyun.com/wanxiang/") + print(" 或获取API: https://bailian.console.aliyun.com/") + return None + + url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis" + headers = { + "Authorization": f"Bearer {DASHSCOPE_API_KEY}", + "Content-Type": "application/json", + } + data = { + "model": model, + "input": {"prompt": prompt}, + "parameters": { + "size": "1024*1024", + "n": 1, + "style": "", + } + } + resp = requests.post(url, headers=headers, json=data, timeout=30) + result = resp.json() + if resp.status_code != 200: + print(f" ❌ 错误 {resp.status_code}: {result}") + return None + + task_id = result.get("output", {}).get("task_id") + if not task_id: + print(f" ❌ 无task_id: {result}") + return None + + # 轮询结果 + print(f" ⏳ 任务ID: {task_id},等待生成...") + for i in range(30): + time.sleep(2) + status_url = f"https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}" + s = requests.get(status_url, headers=headers, timeout=10).json() + status = s.get("output", {}).get("task_status", "") + print(f" [{i+1}] {status}") + if status == "succeeded": + img_url = s["output"]["results"][0]["image_url"] + return save_image(img_url) + elif status == "failed": + print(f" ❌ 生成失败: {s}") + return None + print(" ❌ 超时") + return None + +# ─── Pollinations(免费,无需API Key)────────────────── + +def pollinations_image(prompt, width=1024, height=1024, model=None): + """Pollinations - 完全免费的AI生图""" + encoded_prompt = urllib.parse.quote(prompt) + url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width={width}&height={height}" + if model: + url += f"&model={model}" + save_path = f"/tmp/pollen_{int(time.time())}.png" + print(f" 🔗 URL: {url}") + print(f" ⏳ 生成中(通常10-30秒)...") + try: + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}) + with urllib.request.urlopen(req, timeout=60) as resp: + with open(save_path, "wb") as f: + f.write(resp.read()) + size = os.path.getsize(save_path) + print(f" ✅ 已保存: {save_path} ({size//1024}KB)") + return save_path + except Exception as e: + print(f" ❌ 下载失败: {e}") + return None + +def pollinations_video(prompt, duration=5): + """Pollinations - 免费视频生成(experimental)""" + encoded_prompt = urllib.parse.quote(prompt) + url = f"https://video.pollinations.ai/v1?prompt={encoded_prompt}&duration={duration}" + save_path = f"/tmp/pollen_vid_{int(time.time())}.mp4" + print(f" 🔗 {url}") + try: + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}) + with urllib.request.urlopen(req, timeout=60) as resp: + with open(save_path, "wb") as f: + f.write(resp.read()) + size = os.path.getsize(save_path) + print(f" ✅ 已保存: {save_path} ({size//1024}KB)") + return save_path + except Exception as e: + print(f" ❌ 下载失败: {e}") + return None + +# ─── 海螺AI (MiniMax) ───────────────────────────────── + +def hailuo_image(prompt, model="image-01"): + """海螺AI - 图像生成""" + if not MINIMAX_API_KEY: + print(" ⚠️ 未设置 MINIMAX_API_KEY") + print(" 获取: https://platform.minimaxi.com/") + return None + + url = "https://api.minimax.chat/v1/image_generation" + headers = { + "Authorization": f"Bearer {MINIMAX_API_KEY}", + "Content-Type": "application/json", + } + data = { + "model": model, + "prompt": prompt, + "aspect_ratio": "1:1", + "response_format": "url", + } + resp = requests.post(url, headers=headers, json=data, timeout=30) + result = resp.json() + if "data" in result and result["data"]: + img_url = result["data"][0]["url"] + return save_image(img_url) + else: + print(f" ❌ 错误: {result}") + return None + +def hailuo_video(prompt, model="video-01"): + """海螺AI - 视频生成(推荐,免费额度高)""" + if not MINIMAX_API_KEY or not MINIMAX_GROUP_ID: + print(" ⚠️ 未设置 MINIMAX_API_KEY 或 MINIMAX_GROUP_ID") + print(" 获取: https://platform.minimaxi.com/") + return None + + url = "https://api.minimax.chat/v1/video_generation" + headers = { + "Authorization": f"Bearer {MINIMAX_API_KEY}", + "Content-Type": "application/json", + "GroupId": MINIMAX_GROUP_ID, + } + data = { + "model": model, + "prompt": prompt, + "duration": 5, + "resolution": "720p", + "response_format": "url", + } + resp = requests.post(url, headers=headers, json=data, timeout=30) + result = resp.json() + print(f" 📋 响应: {json.dumps(result, ensure_ascii=False)[:200]}") + + task_id = result.get("data", {}).get("task_id") or result.get("task_id") + if not task_id: + print(f" ❌ 无task_id: {result}") + return None + + print(f" ⏳ 任务ID: {task_id},等待生成(视频约需30-60秒)...") + for i in range(60): + time.sleep(3) + # 查询状态 + poll_url = f"https://api.minimax.chat/v1/video_generation?task_id={task_id}" + s = requests.get(poll_url, headers=headers, timeout=10).json() + status = s.get("data", {}).get("status", "processing") + print(f" [{i+1}] {status}") + if status == "success" or status == "completed": + video_url = s["data"].get("video_info", {}).get("video_url", "") + if not video_url: + # 尝试其他字段 + video_url = s["data"].get("video_url", "") + if video_url: + return save_video(video_url) + print(f" 📋 结果: {json.dumps(s, ensure_ascii=False)[:300]}") + return None + elif status == "failed": + print(f" ❌ 生成失败: {s}") + return None + print(" ❌ 超时") + return None + +# ─── 主程序 ─────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description="AI 图片/视频生成工具") + sub = parser.add_subparsers(dest="cmd") + + p_img = sub.add_parser("image", help="生成图片") + p_img.add_argument("prompt", help="图片描述") + p_img.add_argument("--model", default="pollen", help="模型: pollen(默认/免费), wanxiang, hailuo") + p_img.add_argument("--width", type=int, default=1024) + p_img.add_argument("--height", type=int, default=1024) + + p_vid = sub.add_parser("video", help="生成视频") + p_vid.add_argument("prompt", help="视频描述") + p_vid.add_argument("--model", default="hailuo", help="模型: hailuo (默认 hailuo)") + + sub.add_parser("status", help="查看配置状态") + + args = parser.parse_args(sys.argv[1:] if len(sys.argv) > 1 else ["status"]) + + if args.cmd == "status": + print("=" * 40) + print("AI 生成工具状态") + print("=" * 40) + print(f" Pollinations (图像): ✅ 随时可用(免费)") + has_dash = bool(DASHSCOPE_API_KEY) + has_mini = bool(MINIMAX_API_KEY) + print(f" 通义万相 (图像): {'✅ 已配置' if has_dash else '❌ 未配置'}") + print(f" 海螺AI (图像+视频): {'✅ 已配置' if has_mini else '❌ 未配置'}") + print() + print("获取API密钥:") + print(" 通义万相: https://bailian.console.aliyun.com/ (有免费额度)") + print(" 海螺AI: https://platform.minimaxi.com/") + print() + print("使用方法:") + print(" python3 ai_gen.py image '一只橘猫' # 默认 Pollinations") + print(" python3 ai_gen.py image '一只橘猫' --width 1024 --height 1024") + print(" python3 ai_gen.py image '风景' --model wanxiang # 通义万相") + print(" python3 ai_gen.py video '海浪拍打沙滩' # 海螺AI视频") + print(" python3 ai_gen.py status") + return + + if args.cmd == "image": + print(f"\n🎨 生成图片: {args.prompt}") + if args.model == "pollen": + print(" 平台: Pollinations(免费,无需API Key)") + result = pollinations_image(args.prompt, width=args.width, height=args.height) + elif args.model == "wanxiang": + print(" 平台: 通义万相") + result = wanxiang_image(args.prompt) + elif args.model == "hailuo": + print(" 平台: 海螺AI") + result = hailuo_image(args.prompt) + else: + print(f" ❌ 未知模型: {args.model}") + result = None + if result: + print(f"\n✅ 完成: {result}") + + elif args.cmd == "video": + print(f"\n🎬 生成视频: {args.prompt}") + result = hailuo_video(args.prompt) + if result: + print(f"\n✅ 完成: {result}") + + else: + parser.print_help() + +if __name__ == "__main__": + main() \ No newline at end of file