change to url
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
|
||||||
|
warnings.warn(
|
||||||
|
INFO: Started server process [9669]
|
||||||
|
INFO: Waiting for application startup.
|
||||||
|
INFO: Application startup complete.
|
||||||
|
INFO: Uvicorn running on http://0.0.0.0:12222 (Press CTRL+C to quit)
|
||||||
+56
-8
@@ -10,6 +10,8 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -21,6 +23,8 @@ from fastapi.responses import FileResponse
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
import oss2
|
||||||
|
|
||||||
COMFYUI_URL = "http://127.0.0.1:8188"
|
COMFYUI_URL = "http://127.0.0.1:8188"
|
||||||
# COMFYUI_URL = "http://117.50.80.187:47697"
|
# COMFYUI_URL = "http://117.50.80.187:47697"
|
||||||
WORKFLOW_PATH = Path(__file__).parent / "change2_1_ex.json"
|
WORKFLOW_PATH = Path(__file__).parent / "change2_1_ex.json"
|
||||||
@@ -40,6 +44,37 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def upload_to_oss(image_path, object_name=None):
|
||||||
|
# 配置信息(替换为你的实际信息)
|
||||||
|
access_key_id = 'LTAI5tGp1sLzedqxihcNC1eb'
|
||||||
|
access_key_secret = 'IFZE1b8YYreCP6zfA6GaZ9uBT678qO'
|
||||||
|
endpoint = 'oss-cn-beijing.aliyuncs.com' # 替换为你的Endpoint
|
||||||
|
bucket_name = 'xiangsilian'
|
||||||
|
|
||||||
|
# 创建Bucket实例
|
||||||
|
auth = oss2.Auth(access_key_id, access_key_secret)
|
||||||
|
bucket = oss2.Bucket(auth, endpoint, bucket_name)
|
||||||
|
|
||||||
|
# 如果没有指定OSS文件名,则使用本地文件名
|
||||||
|
if object_name is None:
|
||||||
|
object_name = image_path.split('/')[-1] # 取本地文件名
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 上传文件
|
||||||
|
bucket.put_object_from_file(object_name, image_path)
|
||||||
|
|
||||||
|
# 获取文件URL(有效期默认10年)
|
||||||
|
url = bucket.sign_url('GET', object_name, 3600 * 24 * 365 * 10)
|
||||||
|
|
||||||
|
# 或者使用公共读Bucket的URL(如果Bucket是公共读权限)
|
||||||
|
# url = f"https://{bucket_name}.{endpoint}/{object_name}"
|
||||||
|
|
||||||
|
print(f"文件上传成功,URL: {url}")
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
print(f"上传失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TryOnRequest(BaseModel):
|
class TryOnRequest(BaseModel):
|
||||||
model_image: str # base64 模特图片
|
model_image: str # base64 模特图片
|
||||||
@@ -47,7 +82,7 @@ class TryOnRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TryOnResponse(BaseModel):
|
class TryOnResponse(BaseModel):
|
||||||
result_image: str # base64 结果图片
|
result_url: str # OSS 图片 URL
|
||||||
|
|
||||||
|
|
||||||
def decode_base64_image(b64_str: str) -> bytes:
|
def decode_base64_image(b64_str: str) -> bytes:
|
||||||
@@ -96,14 +131,12 @@ async def wait_for_result(client: httpx.AsyncClient, prompt_id: str, timeout: in
|
|||||||
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
|
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
|
||||||
|
|
||||||
|
|
||||||
async def fetch_image_as_base64(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> str:
|
async def fetch_image_bytes(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> bytes:
|
||||||
"""从 ComfyUI 下载图片并编码为 base64"""
|
"""从 ComfyUI 下载图片,返回原始字节"""
|
||||||
params = {"filename": filename, "subfolder": subfolder, "type": type_}
|
params = {"filename": filename, "subfolder": subfolder, "type": type_}
|
||||||
resp = await client.get(f"{COMFYUI_URL}/view", params=params)
|
resp = await client.get(f"{COMFYUI_URL}/view", params=params)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
content_type = resp.headers.get("content-type", "image/png")
|
return resp.content
|
||||||
b64 = base64.b64encode(resp.content).decode()
|
|
||||||
return f"data:{content_type};base64,{b64}"
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/try-on", response_model=TryOnResponse)
|
@app.post("/try-on", response_model=TryOnResponse)
|
||||||
@@ -173,9 +206,24 @@ async def try_on(req: TryOnRequest):
|
|||||||
|
|
||||||
# 下载结果图片
|
# 下载结果图片
|
||||||
async with httpx.AsyncClient(timeout=30.0) as dl_client:
|
async with httpx.AsyncClient(timeout=30.0) as dl_client:
|
||||||
result_b64 = await fetch_image_as_base64(dl_client, filename, subfolder, type_)
|
result_bytes = await fetch_image_bytes(dl_client, filename, subfolder, type_)
|
||||||
|
|
||||||
return TryOnResponse(result_image=result_b64)
|
# 将图片保存到临时文件,上传到 OSS
|
||||||
|
suffix = Path(filename).suffix or ".png"
|
||||||
|
object_name = f"tryon/{uuid.uuid4().hex}{suffix}"
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
||||||
|
tmp.write(result_bytes)
|
||||||
|
tmp_path = tmp.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
result_url = upload_to_oss(tmp_path, object_name)
|
||||||
|
finally:
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
|
||||||
|
if not result_url:
|
||||||
|
raise HTTPException(status_code=500, detail="上传结果图片到 OSS 失败")
|
||||||
|
|
||||||
|
return TryOnResponse(result_url=result_url)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
@@ -309,15 +309,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
resultImg.src = data.result_image;
|
resultImg.src = data.result_url;
|
||||||
resultSection.classList.add('visible');
|
resultSection.classList.add('visible');
|
||||||
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
|
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
|
||||||
|
|
||||||
// 下载按钮
|
// 下载按钮
|
||||||
document.getElementById('btn-download').onclick = () => {
|
document.getElementById('btn-download').onclick = () => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = data.result_image;
|
a.href = data.result_url;
|
||||||
a.download = `tryon_${Date.now()}.png`;
|
a.download = `tryon_${Date.now()}.png`;
|
||||||
|
a.target = '_blank';
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
|
||||||
|
warnings.warn(
|
||||||
|
INFO: Started server process [9612]
|
||||||
|
INFO: Waiting for application startup.
|
||||||
|
INFO: Application startup complete.
|
||||||
|
INFO: Uvicorn running on http://0.0.0.0:12223 (Press CTRL+C to quit)
|
||||||
|
静态文件目录: /root/change_cloth_server/change3/static
|
||||||
|
接收到换装请求,正在处理...
|
||||||
|
文件上传成功,URL: http://xiangsilian.oss-cn-beijing.aliyuncs.com/tryon%2F627bcee434384a7b847faf2dbbce90ae.png?OSSAccessKeyId=LTAI5tGp1sLzedqxihcNC1eb&Expires=2088730306&Signature=aATPXbjJCE3ArJnYfRyvSVSI8EE%3D
|
||||||
|
INFO: 127.0.0.1:39178 - "POST /try-on HTTP/1.1" 200 OK
|
||||||
+47
-8
@@ -10,11 +10,14 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import oss2
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
@@ -41,6 +44,28 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_oss(image_path, object_name=None):
|
||||||
|
access_key_id = 'LTAI5tGp1sLzedqxihcNC1eb'
|
||||||
|
access_key_secret = 'IFZE1b8YYreCP6zfA6GaZ9uBT678qO'
|
||||||
|
endpoint = 'oss-cn-beijing.aliyuncs.com'
|
||||||
|
bucket_name = 'xiangsilian'
|
||||||
|
|
||||||
|
auth = oss2.Auth(access_key_id, access_key_secret)
|
||||||
|
bucket = oss2.Bucket(auth, endpoint, bucket_name)
|
||||||
|
|
||||||
|
if object_name is None:
|
||||||
|
object_name = image_path.split('/')[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
bucket.put_object_from_file(object_name, image_path)
|
||||||
|
url = bucket.sign_url('GET', object_name, 3600 * 24 * 365 * 10)
|
||||||
|
print(f"文件上传成功,URL: {url}")
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
print(f"上传失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TryOnRequest(BaseModel):
|
class TryOnRequest(BaseModel):
|
||||||
model_image: str # base64 模特图片
|
model_image: str # base64 模特图片
|
||||||
shirt_image: str # base64 上衣图片
|
shirt_image: str # base64 上衣图片
|
||||||
@@ -48,7 +73,7 @@ class TryOnRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TryOnResponse(BaseModel):
|
class TryOnResponse(BaseModel):
|
||||||
result_image: str # base64 结果图片
|
result_url: str # OSS 图片 URL
|
||||||
|
|
||||||
|
|
||||||
def decode_base64_image(b64_str: str) -> bytes:
|
def decode_base64_image(b64_str: str) -> bytes:
|
||||||
@@ -97,14 +122,12 @@ async def wait_for_result(client: httpx.AsyncClient, prompt_id: str, timeout: in
|
|||||||
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
|
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
|
||||||
|
|
||||||
|
|
||||||
async def fetch_image_as_base64(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> str:
|
async def fetch_image_bytes(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> bytes:
|
||||||
"""从 ComfyUI 下载图片并编码为 base64"""
|
"""从 ComfyUI 下载图片,返回原始字节"""
|
||||||
params = {"filename": filename, "subfolder": subfolder, "type": type_}
|
params = {"filename": filename, "subfolder": subfolder, "type": type_}
|
||||||
resp = await client.get(f"{COMFYUI_URL}/view", params=params)
|
resp = await client.get(f"{COMFYUI_URL}/view", params=params)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
content_type = resp.headers.get("content-type", "image/png")
|
return resp.content
|
||||||
b64 = base64.b64encode(resp.content).decode()
|
|
||||||
return f"data:{content_type};base64,{b64}"
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/try-on", response_model=TryOnResponse)
|
@app.post("/try-on", response_model=TryOnResponse)
|
||||||
@@ -116,6 +139,7 @@ async def try_on(req: TryOnRequest):
|
|||||||
- pants_image: 裤子图片 base64
|
- pants_image: 裤子图片 base64
|
||||||
返回 result_image: 换装结果 base64
|
返回 result_image: 换装结果 base64
|
||||||
"""
|
"""
|
||||||
|
print(f"接收到换装请求,正在处理...")
|
||||||
# 加载工作流模板
|
# 加载工作流模板
|
||||||
workflow = json.loads(WORKFLOW_PATH.read_text(encoding="utf-8"))
|
workflow = json.loads(WORKFLOW_PATH.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
@@ -175,9 +199,24 @@ async def try_on(req: TryOnRequest):
|
|||||||
|
|
||||||
# 下载结果图片
|
# 下载结果图片
|
||||||
async with httpx.AsyncClient(timeout=30.0) as dl_client:
|
async with httpx.AsyncClient(timeout=30.0) as dl_client:
|
||||||
result_b64 = await fetch_image_as_base64(dl_client, filename, subfolder, type_)
|
result_bytes = await fetch_image_bytes(dl_client, filename, subfolder, type_)
|
||||||
|
|
||||||
return TryOnResponse(result_image=result_b64)
|
# 将图片保存到临时文件,上传到 OSS
|
||||||
|
suffix = Path(filename).suffix or ".png"
|
||||||
|
object_name = f"tryon/{uuid.uuid4().hex}{suffix}"
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
||||||
|
tmp.write(result_bytes)
|
||||||
|
tmp_path = tmp.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
result_url = upload_to_oss(tmp_path, object_name)
|
||||||
|
finally:
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
|
||||||
|
if not result_url:
|
||||||
|
raise HTTPException(status_code=500, detail="上传结果图片到 OSS 失败")
|
||||||
|
|
||||||
|
return TryOnResponse(result_url=result_url)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
@@ -320,15 +320,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
resultImg.src = data.result_image;
|
resultImg.src = data.result_url;
|
||||||
resultSection.classList.add('visible');
|
resultSection.classList.add('visible');
|
||||||
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
|
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
|
||||||
|
|
||||||
// 下载按钮
|
// 下载按钮
|
||||||
document.getElementById('btn-download').onclick = () => {
|
document.getElementById('btn-download').onclick = () => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = data.result_image;
|
a.href = data.result_url;
|
||||||
a.download = `tryon_${Date.now()}.png`;
|
a.download = `tryon_${Date.now()}.png`;
|
||||||
|
a.target = '_blank';
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ def change_cloth_base64():
|
|||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
result = response.json()
|
result = response.json()
|
||||||
result_image = result.get('result_image')
|
result_image = result.get('result_url')
|
||||||
|
|
||||||
if not result_image:
|
if not result_image:
|
||||||
return jsonify({"ret": -1, "state": -1, "msg": "Backend returned no image"}), 500
|
return jsonify({"ret": -1, "state": -1, "msg": "Backend returned no image"}), 500
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
|
||||||
|
warnings.warn(
|
||||||
|
* Serving Flask app 'change_app'
|
||||||
|
* Debug mode: off
|
||||||
|
[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||||
|
* Running on all addresses (0.0.0.0)
|
||||||
|
* Running on http://127.0.0.1:28888
|
||||||
|
* Running on http://10.60.190.115:28888
|
||||||
|
[33mPress CTRL+C to quit[0m
|
||||||
|
1.202.76.38 - - [13/Mar/2026 02:51:46] "POST /change_cloth_base64 HTTP/1.1" 200 -
|
||||||
+3
-6
@@ -342,12 +342,8 @@
|
|||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
// 显示原始响应(data 字段截断)
|
// 显示原始响应
|
||||||
const preview = { ...data };
|
rawText.textContent = JSON.stringify(data, null, 2);
|
||||||
if (preview.data && preview.data.length > 80) {
|
|
||||||
preview.data = preview.data.slice(0, 80) + '…(已截断)';
|
|
||||||
}
|
|
||||||
rawText.textContent = JSON.stringify(preview, null, 2);
|
|
||||||
rawResp.classList.add('visible');
|
rawResp.classList.add('visible');
|
||||||
|
|
||||||
if (data.ret !== 0 || !data.data) {
|
if (data.ret !== 0 || !data.data) {
|
||||||
@@ -362,6 +358,7 @@
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = data.data;
|
a.href = data.data;
|
||||||
a.download = `tryon_${Date.now()}.png`;
|
a.download = `tryon_${Date.now()}.png`;
|
||||||
|
a.target = '_blank';
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user