Files
ai_game/server/app/services/image_gen.py

140 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
图片生成服务 - 使用 Gemini API 生图,存储到本地/云托管
"""
import httpx
import base64
import time
import hashlib
import os
from typing import Optional
from app.config import get_settings
# 图片尺寸规范(基于前端展示尺寸 × 3倍清晰度
IMAGE_SIZES = {
"cover": {"width": 240, "height": 330, "desc": "竖版封面图3:4比例"},
"avatar": {"width": 150, "height": 150, "desc": "正方形头像1:1比例"},
"background": {"width": 1120, "height": 840, "desc": "横版背景图4:3比例"},
"character": {"width": 512, "height": 768, "desc": "竖版角色立绘2:3比例透明背景"}
}
class ImageGenService:
def __init__(self):
settings = get_settings()
self.api_key = settings.gemini_api_key
self.base_url = "https://work.poloapi.com/v1beta"
# 计算绝对路径
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', settings.upload_path))
self.upload_dir = os.path.join(base_dir, "images")
os.makedirs(self.upload_dir, exist_ok=True)
def get_size_prompt(self, image_type: str) -> str:
"""获取尺寸描述提示词"""
size = IMAGE_SIZES.get(image_type, IMAGE_SIZES["background"])
return f"Image size: {size['width']}x{size['height']} pixels, {size['desc']}."
async def generate_image(self, prompt: str, image_type: str = "background", style: str = "anime") -> Optional[dict]:
"""
调用 Gemini 生成图片
prompt: 图片描述
image_type: 图片类型cover/avatar/background/character
style: 风格anime/realistic/illustration
"""
style_prefix = {
"anime": "anime style, high quality illustration, vibrant colors, ",
"realistic": "photorealistic, high detail, cinematic lighting, ",
"illustration": "digital art illustration, beautiful artwork, "
}
# 组合完整提示词:风格 + 尺寸 + 内容
size_prompt = self.get_size_prompt(image_type)
full_prompt = f"{style_prefix.get(style, '')}{size_prompt} {prompt}"
try:
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
f"{self.base_url}/models/gemini-3-pro-image-preview:generateContent",
headers={
"Content-Type": "application/json",
"x-goog-api-key": self.api_key
},
json={
"contents": [{
"parts": [{
"text": f"Generate an image: {full_prompt}"
}]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"]
}
}
)
if response.status_code == 200:
data = response.json()
candidates = data.get("candidates", [])
if candidates:
parts = candidates[0].get("content", {}).get("parts", [])
for part in parts:
if "inlineData" in part:
return {
"success": True,
"image_data": part["inlineData"]["data"],
"mime_type": part["inlineData"].get("mimeType", "image/png")
}
return {"success": False, "error": "No image in response"}
else:
error_text = response.text[:200]
return {"success": False, "error": f"API error: {response.status_code} - {error_text}"}
except Exception as e:
return {"success": False, "error": str(e)}
async def save_image(self, image_data: str, filename: str) -> Optional[str]:
"""保存图片到本地返回访问URL"""
try:
image_bytes = base64.b64decode(image_data)
file_path = os.path.join(self.upload_dir, filename)
with open(file_path, "wb") as f:
f.write(image_bytes)
# 返回可访问的URL路径
return f"/uploads/images/{filename}"
except Exception as e:
print(f"Save error: {e}")
return None
async def generate_and_save(self, prompt: str, image_type: str = "background", style: str = "anime") -> dict:
"""生成图片并保存"""
result = await self.generate_image(prompt, image_type, style)
if not result or not result.get("success"):
return {
"success": False,
"error": result.get("error", "生成失败") if result else "生成失败"
}
# 生成文件名
timestamp = int(time.time() * 1000)
hash_str = hashlib.md5(prompt.encode()).hexdigest()[:8]
ext = "png" if "png" in result.get("mime_type", "") else "jpg"
filename = f"{image_type}_{timestamp}_{hash_str}.{ext}"
url = await self.save_image(result["image_data"], filename)
if url:
return {"success": True, "url": url, "filename": filename}
else:
return {"success": False, "error": "保存失败"}
# 延迟初始化单例
_service_instance = None
def get_image_gen_service():
global _service_instance
if _service_instance is None:
_service_instance = ImageGenService()
return _service_instance