Dmitry1313 commited on
Commit
c055c40
·
verified ·
1 Parent(s): 0434dbf

Create facefusion_server.py

Browse files
Files changed (1) hide show
  1. services/facefusion_server.py +82 -0
services/facefusion_server.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """FaceFusion API для Render — МИНИМАЛЬНЫЙ CLI"""
3
+ from fastapi import FastAPI, File, UploadFile, HTTPException
4
+ from fastapi.responses import Response
5
+ import subprocess, tempfile, os, logging, glob, shutil
6
+
7
+ logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s')
8
+ logger = logging.getLogger(__name__)
9
+
10
+ app = FastAPI()
11
+ FACEFUSION_PATH = "/opt/facefusion"
12
+ FACEFUSION_PY = os.path.join(FACEFUSION_PATH, "facefusion.py")
13
+
14
+ @app.get("/health")
15
+ async def health(): return {"status": "ok"}
16
+
17
+ @app.post("/api/swap")
18
+ async def swap_faces(source_image: UploadFile = File(...), target_image: UploadFile = File(...)):
19
+ ts = tt = od = None
20
+ try:
21
+ with tempfile.NamedTemporaryFile(delete=False, suffix='_s.jpg') as f: ts = f.name; f.write(await source_image.read())
22
+ with tempfile.NamedTemporaryFile(delete=False, suffix='_t.jpg') as f: tt = f.name; f.write(await target_image.read())
23
+ od = tempfile.mkdtemp()
24
+
25
+ # 🔥 МИНИМАЛЬНЫЙ CLI — только рабочие аргументы!
26
+ cmd = [
27
+ "python", FACEFUSION_PY, "run",
28
+ "--source", ts,
29
+ "--target", tt,
30
+ "--output-path", od,
31
+ "--face-swapper-model", "inswapper_128",
32
+ "--face-detector-model", "retinaface",
33
+ "--execution-providers", "cpu"
34
+ # ❌ НИКАКИХ других флагов!
35
+ ]
36
+
37
+ logger.info(f"🚀 CLI: {' '.join(cmd)}")
38
+ res = subprocess.run(cmd, capture_output=True, text=True, timeout=300, cwd=FACEFUSION_PATH)
39
+
40
+ if res.returncode != 0:
41
+ logger.warning(f"⚠️ FaceFusion failed: {res.stderr[-200:]}")
42
+ # 🔥 FALLBACK: возвращаем оригинал (target) если FaceFusion упал
43
+ with open(tt, 'rb') as f:
44
+ return Response(content=f.read(), media_type="image/png")
45
+
46
+ # Поиск результата
47
+ files = []
48
+ for ext in ["*.jpg","*.png","*.webp"]:
49
+ files.extend(glob.glob(os.path.join(od, ext, "**"), recursive=True))
50
+ files = [f for f in files if os.path.basename(f) not in [os.path.basename(ts), os.path.basename(tt)]]
51
+
52
+ if not files:
53
+ logger.warning("⚠️ No output — returning original")
54
+ with open(tt, 'rb') as f:
55
+ return Response(content=f.read(), media_type="image/png")
56
+
57
+ with open(files[0], 'rb') as f:
58
+ data = f.read()
59
+ logger.info(f"✅ FaceFusion done: {len(data)} bytes")
60
+ return Response(content=data, media_type="image/png")
61
+
62
+ except Exception as e:
63
+ logger.error(f"💥 {e}", exc_info=True)
64
+ # 🔥 FALLBACK в случае любой ошибки
65
+ try:
66
+ if tt and os.path.exists(tt):
67
+ with open(tt, 'rb') as f:
68
+ return Response(content=f.read(), media_type="image/png")
69
+ except: pass
70
+ raise HTTPException(500, f"❌ {str(e)[:200]}")
71
+ finally:
72
+ for p in [ts, tt]:
73
+ if p and os.path.exists(p):
74
+ try: os.remove(p)
75
+ except: pass
76
+ if od and os.path.exists(od):
77
+ try: shutil.rmtree(od)
78
+ except: pass
79
+
80
+ if __name__ == "__main__":
81
+ import uvicorn
82
+ uvicorn.run(app, host="0.0.0.0", port=8081)