feat: automate image pipeline and simplify gemini flow

This commit is contained in:
2026-03-08 23:42:18 +08:00
parent 3a78eb304a
commit 82284ce3fb
6 changed files with 660 additions and 520 deletions

View File

@@ -7,8 +7,9 @@ import os
import httpx
import logging
import mimetypes
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Tuple
from typing import Iterator, Optional
from dotenv import load_dotenv
logger = logging.getLogger(__name__)
@@ -17,11 +18,41 @@ load_dotenv()
# 图绘平台配置
TUHUI_BASE_URL = os.getenv("TUHUI_BASE_URL", "https://tuhui.cloud")
TUHUI_FALLBACK_BASE_URL = "https://tuhui.cloud"
TUHUI_WEB_BASE_URL = os.getenv("TUHUI_WEB_BASE_URL", "https://tuhui.cloud").rstrip("/")
TUHUI_PHONE = os.getenv("TUHUI_PHONE", "17520145271") # 图绘账号手机号
TUHUI_PASSWORD = os.getenv("TUHUI_PASSWORD", "zuowei1216") # 图绘账号密码
TUHUI_DEFAULT_PRICE = int(os.getenv("TUHUI_DEFAULT_PRICE", "20")) # 默认定价(元)
TUHUI_DEFAULT_CATEGORY = os.getenv("TUHUI_DEFAULT_CATEGORY", "设计素材")
@dataclass
class TuhuiUploadResult:
"""图绘上传结果。主返回 URL 为站内作品页,保留三元组解包兼容。"""
success: bool
download_url: str
work_id: int
image_url: str = ""
thumbnail_url: str = ""
watermarked_url: str = ""
message: str = ""
def __iter__(self) -> Iterator[object]:
# 兼容历史调用ok, download_url, work_id = result
yield self.success
yield self.download_url
yield self.work_id
def as_dict(self) -> dict:
return {
"success": self.success,
"download_url": self.download_url,
"work_id": self.work_id,
"image_url": self.image_url,
"thumbnail_url": self.thumbnail_url,
"watermarked_url": self.watermarked_url,
"message": self.message,
}
class TuhuiUploadService:
"""图绘平台上传服务"""
@@ -49,6 +80,10 @@ class TuhuiUploadService:
def _api_url(self, path: str) -> str:
return self._build_api_url(self.base_url, path)
@staticmethod
def _build_work_url(work_id: int) -> str:
return f"{TUHUI_WEB_BASE_URL}/detail/{int(work_id)}"
@staticmethod
def _guess_file_meta(image_path: str) -> tuple[str, str]:
path = Path(image_path)
@@ -97,7 +132,8 @@ class TuhuiUploadService:
price: Optional[int] = None,
category: str = TUHUI_DEFAULT_CATEGORY,
tags: str = "",
) -> Tuple[bool, str, int]:
designer_name: str = "",
) -> TuhuiUploadResult:
"""
上传图片到图绘平台
@@ -109,16 +145,19 @@ class TuhuiUploadService:
category: 分类
Returns:
(success, image_url, work_id)
TuhuiUploadResult
- success: 是否上传成功
- image_url: 图片 URL
- download_url: 站内作品页地址
- image_url: 原图 URL保留便于需要时取用
- thumbnail_url: 缩略图 URL
- watermarked_url: 水印图 URL
- work_id: 作品 ID
"""
try:
# 如果 token 过期,重新登录
if not self.access_token:
if not await self.login():
return False, "登录失败", 0
return TuhuiUploadResult(False, "", 0, message="登录失败")
# 准备上传数据
price = price or self.default_price
@@ -126,7 +165,7 @@ class TuhuiUploadService:
# 读取图片文件
if not os.path.exists(image_path):
logger.error(f"图片文件不存在:{image_path}")
return False, "文件不存在", 0
return TuhuiUploadResult(False, "", 0, message="文件不存在")
filename, mime_type = self._guess_file_meta(image_path)
with open(image_path, "rb") as f:
@@ -142,6 +181,8 @@ class TuhuiUploadService:
}
if tags:
data["tags"] = tags
if designer_name:
data["designer_name"] = str(designer_name).strip()
headers = {
"Authorization": f"Bearer {self.access_token}"
@@ -160,12 +201,34 @@ class TuhuiUploadService:
payload = response.json()
if not payload.get("success", False):
logger.error(f"图绘平台上传返回失败:{payload}")
return False, payload.get("message", "上传失败"), 0
return TuhuiUploadResult(
False,
"",
0,
message=str(payload.get("message", "上传失败")),
)
work_id = int(payload.get("work_id") or payload.get("work", {}).get("id") or 0)
image_url = str(payload.get("image_url") or payload.get("work", {}).get("original_image") or "")
logger.info(f"图绘平台上传成功,作品 ID: {work_id}, URL: {image_url}")
return True, image_url, work_id
thumbnail_url = str(
payload.get("thumbnail_url") or payload.get("work", {}).get("thumbnail_image") or ""
)
watermarked_url = str(
payload.get("watermarked_url") or payload.get("work", {}).get("watermarked_image") or ""
)
download_url = self._build_work_url(work_id) if work_id else ""
logger.info(
f"图绘平台上传成功,作品 ID: {work_id}, 站内地址: {download_url}, 原图: {image_url}"
)
return TuhuiUploadResult(
True,
download_url,
work_id,
image_url=image_url,
thumbnail_url=thumbnail_url,
watermarked_url=watermarked_url,
message=str(payload.get("message", "上传成功")),
)
else:
logger.error(f"图绘平台上传失败:{response.status_code} {response.text}")
@@ -176,13 +239,13 @@ class TuhuiUploadService:
if await self.login():
# 重新上传
return await self.upload_image(
image_path, title, description, price, category
image_path, title, description, price, category, tags, designer_name
)
return False, f"上传失败:{response.text}", 0
return TuhuiUploadResult(False, "", 0, message=f"上传失败:{response.text}")
except Exception as e:
logger.error(f"图绘平台上传异常:{e}")
return False, f"上传异常:{e}", 0
return TuhuiUploadResult(False, "", 0, message=f"上传异常:{e}")
# 单例
@@ -204,12 +267,13 @@ async def upload_to_tuhui(
price: int = 20,
category: str = TUHUI_DEFAULT_CATEGORY,
tags: str = "",
) -> Tuple[bool, str, int]:
designer_name: str = "",
) -> TuhuiUploadResult:
"""
便捷函数:上传图片到图绘平台
Returns:
(success, image_url, work_id)
TuhuiUploadResult
"""
service = get_tuhui_service()
return await service.upload_image(image_path, title, description, price, category, tags)
return await service.upload_image(image_path, title, description, price, category, tags, designer_name)