Spaces:
Paused
Paused
File size: 8,106 Bytes
b5722ce 6dae108 b5722ce 6dae108 b5722ce |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
import os
import uuid
import aiohttp
import aiofiles
import logging
import ssl
from urllib.parse import urlparse
from .config import Config
# 导入上传模块
try:
from .image_uploader import upload_image
UPLOADER_AVAILABLE = True
except ImportError:
UPLOADER_AVAILABLE = False
# 初始化日志
logger = logging.getLogger("sora-api.utils")
# 图片本地化调试开关
IMAGE_DEBUG = os.getenv("IMAGE_DEBUG", "").lower() in ("true", "1", "yes")
# 修复Python 3.11之前版本中HTTPS代理处理HTTPS请求的问题
# 参考: https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support
try:
import aiohttp.connector
orig_create_connection = aiohttp.connector.TCPConnector._create_connection
async def patched_create_connection(self, req, traces, timeout):
if req.ssl and req.proxy and req.proxy.scheme == 'https':
# 为代理连接创建SSL上下文
proxy_ssl = ssl.create_default_context()
req.proxy_ssl = proxy_ssl
if IMAGE_DEBUG:
logger.debug("已应用HTTPS代理补丁")
return await orig_create_connection(self, req, traces, timeout)
# 应用猴子补丁
aiohttp.connector.TCPConnector._create_connection = patched_create_connection
if IMAGE_DEBUG:
logger.debug("已启用aiohttp HTTPS代理支持补丁")
except Exception as e:
logger.warning(f"应用HTTPS代理补丁失败: {e}")
async def download_and_save_image(image_url: str) -> str:
"""
下载图片并保存到本地
Args:
image_url: 图片URL
Returns:
本地化后的图片URL
"""
# 如果未启用本地化或URL已经是本地路径,直接返回
if not Config.IMAGE_LOCALIZATION:
if IMAGE_DEBUG:
logger.debug(f"图片本地化未启用,返回原始URL: {image_url}")
return image_url
# 检查是否已经是本地URL
# 准备URL检测所需的信息
parsed_base_url = urlparse(Config.BASE_URL)
base_path = parsed_base_url.path.rstrip('/')
# 直接检查常见的图片URL模式
local_url_patterns = [
"/images/",
"/static/images/",
f"{base_path}/images/",
f"{base_path}/static/images/"
]
# 检查是否有自定义前缀
if Config.STATIC_PATH_PREFIX:
prefix = Config.STATIC_PATH_PREFIX
if not prefix.startswith('/'):
prefix = f"/{prefix}"
local_url_patterns.append(f"{prefix}/images/")
local_url_patterns.append(f"{base_path}{prefix}/images/")
# 检查是否符合任何本地URL模式
is_local_url = any(pattern in image_url for pattern in local_url_patterns)
if is_local_url:
if IMAGE_DEBUG:
logger.debug(f"URL已是本地图片路径: {image_url}")
# 如果是相对路径,补充完整的URL
if image_url.startswith("/"):
return f"{parsed_base_url.scheme}://{parsed_base_url.netloc}{image_url}"
return image_url
try:
# 生成文件名和保存路径
parsed_url = urlparse(image_url)
file_extension = os.path.splitext(parsed_url.path)[1] or ".png"
filename = f"{uuid.uuid4()}{file_extension}"
save_path = os.path.join(Config.IMAGE_SAVE_DIR, filename)
# 确保保存目录存在
os.makedirs(Config.IMAGE_SAVE_DIR, exist_ok=True)
if IMAGE_DEBUG:
logger.debug(f"下载图片: {image_url} -> {save_path}")
# 配置代理
proxy = None
if Config.PROXY_HOST and Config.PROXY_PORT:
proxy_auth = None
if Config.PROXY_USER and Config.PROXY_PASS:
proxy_auth = aiohttp.BasicAuth(Config.PROXY_USER, Config.PROXY_PASS)
proxy_url = f"http://{Config.PROXY_HOST}:{Config.PROXY_PORT}"
if IMAGE_DEBUG:
auth_info = f" (使用认证)" if proxy_auth else ""
logger.debug(f"使用代理: {proxy_url}{auth_info}")
proxy = proxy_url
# 下载图片
timeout = aiohttp.ClientTimeout(total=60)
async with aiohttp.ClientSession(timeout=timeout) as session:
# 创建请求参数
request_kwargs = {"timeout": 30}
if proxy:
request_kwargs["proxy"] = proxy
if Config.PROXY_USER and Config.PROXY_PASS:
request_kwargs["proxy_auth"] = aiohttp.BasicAuth(Config.PROXY_USER, Config.PROXY_PASS)
async with session.get(image_url, **request_kwargs) as response:
if response.status != 200:
logger.warning(f"下载失败,状态码: {response.status}, URL: {image_url}")
return image_url
content = await response.read()
if not content:
logger.warning("下载内容为空")
return image_url
# 保存图片
async with aiofiles.open(save_path, "wb") as f:
await f.write(content)
# 检查文件是否成功保存
if not os.path.exists(save_path) or os.path.getsize(save_path) == 0:
logger.warning(f"图片保存失败: {save_path}")
if os.path.exists(save_path):
os.remove(save_path)
return image_url
# 尝试上传到PicGo或WebDAV
external_url = None
if UPLOADER_AVAILABLE:
try:
success, url = await upload_image(save_path)
if success and url:
logger.info(f"图片已上传到外部服务: {url}")
external_url = url
except Exception as e:
logger.error(f"上传图片到外部服务时出错: {str(e)}")
# 如果成功上传到外部服务,返回外部URL
if external_url:
return external_url
# 返回本地URL
# 获取文件名
filename = os.path.basename(save_path)
# 解析基础URL
parsed_base_url = urlparse(Config.BASE_URL)
base_path = parsed_base_url.path.rstrip('/')
# 使用固定的图片URL格式
relative_url = f"/images/{filename}"
# 如果设置了STATIC_PATH_PREFIX,优先使用该前缀
if Config.STATIC_PATH_PREFIX:
prefix = Config.STATIC_PATH_PREFIX
if not prefix.startswith('/'):
prefix = f"/{prefix}"
relative_url = f"{prefix}/images/{filename}"
# 如果BASE_URL有子路径,添加到相对路径前
if base_path:
relative_url = f"{base_path}{relative_url}"
# 处理重复斜杠
while "//" in relative_url:
relative_url = relative_url.replace("//", "/")
# 生成完整URL
full_url = f"{parsed_base_url.scheme}://{parsed_base_url.netloc}{relative_url}"
if IMAGE_DEBUG:
logger.debug(f"图片保存成功: {full_url}")
logger.debug(f"图片保存路径: {save_path}")
return full_url
except Exception as e:
logger.error(f"图片下载失败: {str(e)}", exc_info=IMAGE_DEBUG)
return image_url
async def localize_image_urls(image_urls: list) -> list:
"""
批量将图片URL本地化
Args:
image_urls: 图片URL列表
Returns:
本地化后的URL列表
"""
if not Config.IMAGE_LOCALIZATION or not image_urls:
return image_urls
if IMAGE_DEBUG:
logger.debug(f"本地化 {len(image_urls)} 个URL: {image_urls}")
else:
logger.info(f"本地化 {len(image_urls)} 个图片")
localized_urls = []
for url in image_urls:
localized_url = await download_and_save_image(url)
localized_urls.append(localized_url)
return localized_urls |