gallyga commited on
Commit
b5722ce
·
verified ·
1 Parent(s): 2a65f94

Upload utils.py

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