返回 VideoClaw
image_gpt.py
根目录 / video-claw / video-claw / backend / models / image_gpt.py
1 import os
2 import sys
3
4 models_dir = os.path.dirname(os.path.abspath(__file__))
5 backend_dir = os.path.dirname(models_dir)
6 if backend_dir not in sys.path:
7 sys.path.insert(0, backend_dir)
8
9 import time
10 import uuid
11 import base64
12 import httpx
13 import logging
14 from openai import OpenAI
15 from config import Config
16 try:
17 from models.image_processor import ImageProcessor
18 except ImportError:
19 from image_processor import ImageProcessor
20
21 logger = logging.getLogger(__name__)
22
23
24 class ImageGPT:
25 """
26 OpenAI 图片生成客户端
27 支持模型:
28 - sora_image → Images API
29 - gpt-image-2 → Responses API
30 """
31 def __init__(self,
32 api_key: str = None,
33 base_url: str = None,
34 proxy: str = None,
35 timeout: float = 300.0):
36 """
37 OpenAI 图片生成客户端
38 :param api_key: API Key
39 :param base_url: 自定义 Base URL
40 :param proxy: 当前 provider 显式启用时使用的代理
41 :param timeout: 超时时间
42 """
43 self.api_key = api_key or Config.OPENAI_API_KEY
44 self.timeout = timeout
45
46 kwargs = {"api_key": self.api_key, "timeout": self.timeout}
47
48 self.base_url = base_url
49 if proxy is None:
50 proxy = Config.provider_proxy("openai")
51 if proxy:
52 kwargs["http_client"] = httpx.Client(
53 proxy=proxy,
54 timeout=self.timeout,
55 )
56 if self.base_url:
57 kwargs["base_url"] = self.base_url
58
59 self.client = OpenAI(**kwargs)
60 self.max_attempts = 10
61 self.image_processor = ImageProcessor()
62
63 def _encode_image_to_base64(self, image_path: str) -> str:
64 """将本地图片转换为 Base64 编码"""
65 if not image_path or not os.path.exists(image_path):
66 return image_path
67
68 try:
69 with open(image_path, "rb") as f:
70 img_data = base64.b64encode(f.read()).decode("utf-8")
71 ext = os.path.splitext(image_path)[1].lower().replace(".", "")
72 if ext not in ["png", "jpg", "jpeg", "webp"]:
73 ext = "png"
74 return f"data:image/{ext};base64,{img_data}"
75 except Exception as e:
76 logger.warning("Failed to encode image %s: %s", image_path, e)
77 return image_path
78
79 def generate_image(self, prompt, size="1024x1024", quality="high", model="gpt-image-2",
80 save_dir=None, image_urls=None):
81 """Generate a single image, download it, and return the local file path.
82
83 Args:
84 prompt: 图片描述提示词
85 size: 图片尺寸
86 quality: 图片质量
87 model: 模型名称 (sora_image / gpt-image-2)
88 save_dir: 保存目录(不传则返回 URL 或 base64)
89 image_urls: 参考图片 URL 列表(仅 gpt-image-2 支持)
90 """
91
92 attempts = 0
93 last_error = None
94
95 # 处理参考图片
96 extra_body = {}
97 if image_urls and isinstance(image_urls, list) and len(image_urls) > 0:
98 # 中转站通常支持通过 extra_body 传递 image_url 或 ref_image
99 # 这里我们将第一张图作为参考图
100 ref_images = [self._encode_image_to_base64(image_urls[i]) for i in range(min(len(image_urls), 6))]
101 extra_body = {"image_url": ref_images}
102
103 while attempts < self.max_attempts:
104 try:
105 response = self.client.images.generate(
106 model=model,
107 prompt=prompt,
108 size=size,
109 quality=quality,
110 n=1,
111 extra_body=extra_body
112 )
113
114 if not response or not response.data:
115 raise RuntimeError("OpenAI API 返回数据为空")
116
117 img_data = response.data[0]
118 file_path = None
119
120 # 1. 处理 Base64 格式 (中转站常用)
121 if hasattr(img_data, 'b64_json') and img_data.b64_json:
122 if save_dir:
123 os.makedirs(save_dir, exist_ok=True)
124 file_name = f"gpt_{int(time.time())}_{uuid.uuid4().hex[:6]}.png"
125 file_path = os.path.join(save_dir, file_name)
126 with open(file_path, "wb") as f:
127 f.write(base64.b64decode(img_data.b64_json))
128 return file_path
129 return img_data.b64_json
130
131 # 2. 处理 URL 格式
132 elif hasattr(img_data, 'url') and img_data.url:
133 url = img_data.url
134 if save_dir:
135 os.makedirs(save_dir, exist_ok=True)
136 file_name = f"gpt_{int(time.time())}_{uuid.uuid4().hex[:6]}.png"
137 file_path = os.path.join(save_dir, file_name)
138 if self.image_processor.download_image(url, file_path, proxies=Config.requests_proxies("openai")):
139 return file_path
140 return url
141
142 raise RuntimeError("未在响应中找到 url 或 b64_json")
143 except Exception as e:
144 last_error = e
145 # Other errors: wait before retry
146 logger.warning("OpenAI image generation failed; retrying in 10 seconds: %s", e)
147 time.sleep(10)
148 break # Break inner loop to retry all models
149 attempts += 1
150 raise Exception(f"Max attempts reached, failed to generate image. Last error: {last_error}")
151
152 def generate_images(self, prompt, count=4, size="1024x1024", quality="standard", model=None):
153 """Generate multiple image URLs by calling Images API 'count' times."""
154 urls = []
155 for _ in range(count):
156 url = self.generate_image(prompt=prompt, size=size, quality=quality, model=model)
157 urls.append(url)
158 return urls
159
160
161 if __name__ == "__main__":
162 import sys
163 import tempfile
164 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
165 from config import Config
166
167 MODELS = ["gpt-image-2"]
168 save_dir = "code/result/image/test_avail"
169 api_key = Config.OPENAI_API_KEY
170 base_url = Config.OPENAI_BASE_URL
171 if not api_key:
172 print("✗ OPENAI_API_KEY 未设置,跳过")
173 sys.exit(1)
174 print("=== GPT 图片生成测试 ===")
175 print(f" API Key: {api_key[:6]}***")
176 print(f" Base URL: {base_url}")
177
178
179 # 文生图
180 print("\n=== GPT 文生图可用性测试 ===")
181 img_prompt = "A cute orange cat lying on a sunny windowsill, watercolor style"
182 img_path = ""
183 client = ImageGPT(api_key=api_key, base_url=Config.OPENAI_BASE_URL, proxy=Config.provider_proxy("openai"))
184 for model in MODELS:
185 print(f"\nTesting model: {model}")
186 print(f"Prompt: {img_prompt}")
187 print(f"Image path: {img_path}")
188 client.max_attempts = 1
189 t0 = time.time()
190 os.makedirs(save_dir, exist_ok=True)
191 try:
192 path = client.generate_image(prompt=img_prompt, size="1024x1024",
193 model=model, save_dir=save_dir)
194 elapsed = time.time() - t0
195 print(f"✓ 生成成功 ({elapsed:.1f}s): {path}")
196 except Exception as e:
197 elapsed = time.time() - t0
198 print(f"✗ 失败 ({elapsed:.1f}s): {e}")
199
200 # 图生图
201 print("\n=== GPT 图生图可用性测试 ===")
202 img_prompt = "Turn this cat into a cute cartoon character with big eyes and a playful expression"
203 img_path = "code/result/image/test_avail/test_input.jpg"
204 for model in MODELS:
205 print(f"\nTesting model: {model}")
206 print(f"Prompt: {img_prompt}")
207 print(f"Image path: {img_path}")
208 client.max_attempts = 1
209 t0 = time.time()
210 os.makedirs(save_dir, exist_ok=True)
211 try:
212 path = client.generate_image(prompt=img_prompt, size="1024x1024",
213 model=model, save_dir=save_dir, image_urls=[img_path])
214 elapsed = time.time() - t0
215 print(f"✓ 生成成功 ({elapsed:.1f}s): {path}")
216 except Exception as e:
217 elapsed = time.time() - t0
218 print(f"✗ 失败 ({elapsed:.1f}s): {e}")
219
219 lines PYTHON