yaoqifa commited on
Commit
6047ce1
·
verified ·
1 Parent(s): 732f5d6

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -34
app.py CHANGED
@@ -4,6 +4,7 @@ import base64
4
  import os
5
  import asyncio
6
  import time
 
7
  from datetime import datetime
8
  from PIL import Image
9
  from io import BytesIO
@@ -17,6 +18,13 @@ import uvicorn
17
  # ==========================================
18
  # 1. 配置与工具
19
  # ==========================================
 
 
 
 
 
 
 
20
  AR_BASES = {
21
  "1:1 (SDXL标准 1024)": (1024, 1024),
22
  "16:9 (SDXL标准 768p)": (1344, 768),
@@ -43,22 +51,31 @@ class GenerateRequest(BaseModel):
43
 
44
  def process_image_in_memory(image_bytes: bytes, target_format: str) -> str:
45
  try:
46
- img = Image.open(BytesIO(image_bytes))
47
- output_buffer = BytesIO()
48
-
49
- if target_format == "jpg":
50
- img = img.convert("RGB")
51
- img.save(output_buffer, format="JPEG", quality=95)
52
- else:
53
- img.save(output_buffer, format="PNG")
 
54
 
55
- return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
 
 
 
 
56
  except Exception as e:
57
  raise Exception(f"格式转换失败: {str(e)}")
 
 
 
58
 
59
  async def download_image_as_bytes(url):
60
  print(f"📥 [System] 正在下载图片: {url}")
61
- async with httpx.AsyncClient(timeout=45) as client:
 
62
  response = await client.get(url)
63
  if response.status_code == 200:
64
  return response.content
@@ -82,7 +99,7 @@ async def core_generate(req: GenerateRequest):
82
  final_size = f"{int(base_w * multiplier)}x{int(base_h * multiplier)}"
83
  ratio_str = req.ratio.split(" ")[0]
84
 
85
- # 2. 处理 Prompt (加入分辨率指令)
86
  final_prompt = req.prompt
87
  if "gemini" in req.model.lower():
88
  if req.ratio == "custom":
@@ -108,7 +125,6 @@ async def core_generate(req: GenerateRequest):
108
  extra_body={"modalities": ["image", "text"]}
109
  )
110
  content = response.choices[0].message.content
111
- print(f"🟣 [Chat模式] 收到回复 (长度: {len(content)})")
112
 
113
  # 提取链接或 Base64
114
  url_match = re.search(r"!\[.*?\]\((https?://[^\)]+)\)", content)
@@ -122,7 +138,6 @@ async def core_generate(req: GenerateRequest):
122
  b64_str = content.split("base64,")[1].split(")")[0].strip()
123
  image_bytes = base64.b64decode(b64_str)
124
  else:
125
- print(f"❌ [Error] 无法识别: {content[:100]}")
126
  raise Exception("未能识别图片内容")
127
 
128
  else:
@@ -166,31 +181,31 @@ def health():
166
 
167
  @app.post("/api/generate")
168
  async def generate_api(req: GenerateRequest, request: Request):
169
- # --- 日志记录核心部分 ---
170
  client_host = request.client.host
171
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
172
 
173
  print("\n" + "="*60)
174
  print(f"🚀 [New Request] {timestamp} from {client_host}")
175
- print(f"📌 模型 (Model) : {req.model}")
176
- print(f"📐 规格 (Specs) : Ratio={req.ratio} | Scale={req.scale}")
177
- print(f"🎨 格式 (Format) : {req.format}")
178
  print("-" * 60)
179
- print(f"💡 提示词 (Prompt):")
180
- print(f"{req.prompt}")
181
- print("="*60 + "\n")
182
- # -----------------------
183
 
184
- try:
185
- start_time = time.time()
186
- base64_img = await core_generate(req)
187
- duration = time.time() - start_time
188
- print(f"✅ [Success] 生成完成,耗时: {duration:.2f}s")
189
-
190
- return {"status": "success", "image_base64": base64_img}
191
- except Exception as e:
192
- print(f"❌ [Failed] 生成失败: {str(e)}")
193
- return JSONResponse(
194
- content={"status": "error", "message": str(e)},
195
- status_code=200
196
- )
 
 
 
 
 
 
 
 
4
  import os
5
  import asyncio
6
  import time
7
+ import gc # 引入垃圾回收
8
  from datetime import datetime
9
  from PIL import Image
10
  from io import BytesIO
 
18
  # ==========================================
19
  # 1. 配置与工具
20
  # ==========================================
21
+
22
+ # --- 关键修改:全局并发限制 ---
23
+ # 限制同时进行处理的请求数量为 3。
24
+ # 这样即使有 100 个人同时点,同时也只有 3 个人在占用内存处理图片,其他人排队。
25
+ # 既保护了内存,又不会报错。
26
+ CONCURRENCY_LIMITER = asyncio.Semaphore(5)
27
+
28
  AR_BASES = {
29
  "1:1 (SDXL标准 1024)": (1024, 1024),
30
  "16:9 (SDXL标准 768p)": (1344, 768),
 
51
 
52
  def process_image_in_memory(image_bytes: bytes, target_format: str) -> str:
53
  try:
54
+ # 使用上下文管理器打开,确保及时关闭
55
+ with Image.open(BytesIO(image_bytes)) as img:
56
+ output_buffer = BytesIO()
57
+
58
+ if target_format == "jpg":
59
+ img = img.convert("RGB")
60
+ img.save(output_buffer, format="JPEG", quality=95)
61
+ else:
62
+ img.save(output_buffer, format="PNG")
63
 
64
+ result = base64.b64encode(output_buffer.getvalue()).decode('utf-8')
65
+
66
+ # 显式关闭 buffer
67
+ output_buffer.close()
68
+ return result
69
  except Exception as e:
70
  raise Exception(f"格式转换失败: {str(e)}")
71
+ finally:
72
+ # 强制垃圾回收,防止大图片占用内存不释放
73
+ gc.collect()
74
 
75
  async def download_image_as_bytes(url):
76
  print(f"📥 [System] 正在下载图片: {url}")
77
+ # 增加超时时间,防止大图下载断开
78
+ async with httpx.AsyncClient(timeout=60) as client:
79
  response = await client.get(url)
80
  if response.status_code == 200:
81
  return response.content
 
99
  final_size = f"{int(base_w * multiplier)}x{int(base_h * multiplier)}"
100
  ratio_str = req.ratio.split(" ")[0]
101
 
102
+ # 2. 处理 Prompt
103
  final_prompt = req.prompt
104
  if "gemini" in req.model.lower():
105
  if req.ratio == "custom":
 
125
  extra_body={"modalities": ["image", "text"]}
126
  )
127
  content = response.choices[0].message.content
 
128
 
129
  # 提取链接或 Base64
130
  url_match = re.search(r"!\[.*?\]\((https?://[^\)]+)\)", content)
 
138
  b64_str = content.split("base64,")[1].split(")")[0].strip()
139
  image_bytes = base64.b64decode(b64_str)
140
  else:
 
141
  raise Exception("未能识别图片内容")
142
 
143
  else:
 
181
 
182
  @app.post("/api/generate")
183
  async def generate_api(req: GenerateRequest, request: Request):
 
184
  client_host = request.client.host
185
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
186
 
187
  print("\n" + "="*60)
188
  print(f"🚀 [New Request] {timestamp} from {client_host}")
189
+ print(f"📌 模型: {req.model} | 规格: {req.ratio} ({req.scale})")
 
 
190
  print("-" * 60)
 
 
 
 
191
 
192
+ # --- 关键修改:使用 Semaphore 进行限流 ---
193
+ # 只有拿到“通行证”的请求才能进入下面的代码块执行
194
+ # 没拿到的会在这一行等待,直到别人执行完释放内存
195
+ async with CONCURRENCY_LIMITER:
196
+ try:
197
+ start_time = time.time()
198
+ base64_img = await core_generate(req)
199
+ duration = time.time() - start_time
200
+ print(f"✅ [Success] 耗时: {duration:.2f}s")
201
+
202
+ return {"status": "success", "image_base64": base64_img}
203
+ except Exception as e:
204
+ print(f"❌ [Failed] 失败: {str(e)}")
205
+ return JSONResponse(
206
+ content={"status": "error", "message": str(e)},
207
+ status_code=200
208
+ )
209
+ finally:
210
+ # 每次请求结束后,强制清理一次内存
211
+ gc.collect()