| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #!/usr/bin/env python3
- """LibLibAI ComfyUI Workflow Runner
- 用法:
- python liblibai_comfyui_runner.py workflow.json
- python liblibai_comfyui_runner.py workflow.json --output result.png
- """
- import argparse
- import base64
- import hashlib
- import hmac
- import json
- import os
- import sys
- import time
- import uuid
- from pathlib import Path
- import requests
- from dotenv import load_dotenv
- load_dotenv()
- DOMAIN = os.getenv("LIBLIBAI_DOMAIN", "https://openapi.liblibai.cloud")
- ACCESS_KEY = os.getenv("LIBLIBAI_ACCESS_KEY")
- SECRET_KEY = os.getenv("LIBLIBAI_SECRET_KEY")
- DEFAULT_TEMPLATE = "4df2efa0f18d46dc9758803e478eb51c"
- def generate_auth_url(uri: str) -> str:
- """生成带 HMAC-SHA1 签名的完整 URL"""
- ts = str(int(time.time() * 1000))
- nonce = uuid.uuid4().hex
- sign_str = f"{uri}&{ts}&{nonce}"
- dig = hmac.new(SECRET_KEY.encode(), sign_str.encode(), hashlib.sha1).digest()
- signature = base64.urlsafe_b64encode(dig).rstrip(b"=").decode()
- return f"{DOMAIN}{uri}?AccessKey={ACCESS_KEY}&Timestamp={ts}&SignatureNonce={nonce}&Signature={signature}"
- def submit_workflow(workflow_data: dict) -> str:
- """提交 ComfyUI workflow 生图任务"""
- uri = "/api/generate/comfyui/app"
- url = generate_auth_url(uri)
- payload = {
- "templateUuid": DEFAULT_TEMPLATE,
- "generateParams": workflow_data
- }
- print(f"提交任务...")
- resp = requests.post(url, json=payload, headers={"Content-Type": "application/json"})
- resp.raise_for_status()
- data = resp.json()
- if data.get("code") != 0:
- raise Exception(f"提交失败: {data.get('msg', 'unknown error')}")
- generate_uuid = data["data"]["generateUuid"]
- print(f"任务 ID: {generate_uuid}")
- return generate_uuid
- def poll_result(generate_uuid: str, timeout: int = 600) -> dict:
- """轮询任务结果"""
- uri = "/api/generate/comfy/status"
- url = generate_auth_url(uri)
- start_time = time.time()
- print(f"轮询结果 (超时 {timeout}s)...")
- while time.time() - start_time < timeout:
- resp = requests.post(url, json={"generateUuid": generate_uuid})
- resp.raise_for_status()
- data = resp.json()
- if data.get("code") != 0:
- raise Exception(f"查询失败: {data.get('msg', 'unknown error')}")
- result = data["data"]
- status = result["generateStatus"]
- # 1=等待 2=执行中 3=已生图 4=审核中 5=成功 6=失败
- if status in [1, 2, 3, 4]:
- status_text = {1: "等待", 2: "执行中", 3: "已生图", 4: "审核中"}[status]
- print(f" 状态: {status_text} 进度: {result.get('percentCompleted', 0):.0%}")
- time.sleep(5)
- continue
- if status == 5:
- print(f"✓ 任务成功")
- print(f" 消耗积分: {result.get('pointsCost', 0)}")
- print(f" 剩余积分: {result.get('accountBalance', 0)}")
- return result
- if status == 6:
- raise Exception(f"任务失败: {result.get('generateMsg', 'unknown')}")
- raise TimeoutError(f"轮询超时 ({timeout}s)")
- def save_results(result: dict, output_dir: Path):
- """保存生成的图片和视频"""
- output_dir.mkdir(parents=True, exist_ok=True)
- images = result.get("images", [])
- videos = result.get("videos", [])
- print(f"\n生成结果:")
- print(f" 图片: {len(images)} 张")
- print(f" 视频: {len(videos)} 个")
- for i, img in enumerate(images):
- if img.get("auditStatus") != 3:
- print(f" 图片 {i+1}: 审核未通过 (status={img.get('auditStatus')})")
- continue
- url = img["imageUrl"]
- filename = f"image_{i+1}.png"
- filepath = output_dir / filename
- print(f" 下载: {filename}")
- resp = requests.get(url)
- resp.raise_for_status()
- filepath.write_bytes(resp.content)
- print(f" -> {filepath}")
- for i, vid in enumerate(videos):
- if vid.get("auditStatus") != 3:
- print(f" 视频 {i+1}: 审核未通过 (status={vid.get('auditStatus')})")
- continue
- url = vid["videoUrl"]
- filename = f"video_{i+1}.mp4"
- filepath = output_dir / filename
- print(f" 下载: {filename}")
- resp = requests.get(url)
- resp.raise_for_status()
- filepath.write_bytes(resp.content)
- print(f" -> {filepath}")
- def main():
- parser = argparse.ArgumentParser(description="LibLibAI ComfyUI Workflow Runner")
- parser.add_argument("workflow", help="workflow JSON 文件路径")
- parser.add_argument("--output", "-o", default="output", help="输出目录,默认 output/")
- parser.add_argument("--timeout", "-t", type=int, default=600, help="轮询超时秒数,默认 600")
- args = parser.parse_args()
- if not ACCESS_KEY or not SECRET_KEY:
- print("ERROR: 请设置环境变量 LIBLIBAI_ACCESS_KEY 和 LIBLIBAI_SECRET_KEY")
- sys.exit(1)
- workflow_path = Path(args.workflow)
- if not workflow_path.exists():
- print(f"ERROR: 文件不存在: {workflow_path}")
- sys.exit(1)
- print(f"加载 workflow: {workflow_path}")
- with open(workflow_path, "r", encoding="utf-8") as f:
- workflow_data = json.load(f)
- try:
- generate_uuid = submit_workflow(workflow_data)
- result = poll_result(generate_uuid, timeout=args.timeout)
- save_results(result, Path(args.output))
- print("\n✓ 完成")
- except Exception as e:
- print(f"\n✗ 错误: {e}")
- sys.exit(1)
- if __name__ == "__main__":
- main()
|