Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """FaceFusion API для Render — МИНИМАЛЬНЫЙ CLI""" | |
| from fastapi import FastAPI, File, UploadFile, HTTPException | |
| from fastapi.responses import Response | |
| import subprocess, tempfile, os, logging, glob, shutil | |
| logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s') | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI() | |
| FACEFUSION_PATH = "/opt/facefusion" | |
| FACEFUSION_PY = os.path.join(FACEFUSION_PATH, "facefusion.py") | |
| async def health(): return {"status": "ok"} | |
| async def swap_faces(source_image: UploadFile = File(...), target_image: UploadFile = File(...)): | |
| ts = tt = od = None | |
| try: | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='_s.jpg') as f: ts = f.name; f.write(await source_image.read()) | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='_t.jpg') as f: tt = f.name; f.write(await target_image.read()) | |
| od = tempfile.mkdtemp() | |
| # 🔥 МИНИМАЛЬНЫЙ CLI — только рабочие аргументы! | |
| cmd = [ | |
| "python", FACEFUSION_PY, "run", | |
| "--source", ts, | |
| "--target", tt, | |
| "--output-path", od, | |
| "--face-swapper-model", "inswapper_128", | |
| "--face-detector-model", "retinaface", | |
| "--execution-providers", "cpu" | |
| # ❌ НИКАКИХ других флагов! | |
| ] | |
| logger.info(f"🚀 CLI: {' '.join(cmd)}") | |
| res = subprocess.run(cmd, capture_output=True, text=True, timeout=300, cwd=FACEFUSION_PATH) | |
| if res.returncode != 0: | |
| logger.warning(f"⚠️ FaceFusion failed: {res.stderr[-200:]}") | |
| # 🔥 FALLBACK: возвращаем оригинал (target) если FaceFusion упал | |
| with open(tt, 'rb') as f: | |
| return Response(content=f.read(), media_type="image/png") | |
| # Поиск результата | |
| files = [] | |
| for ext in ["*.jpg","*.png","*.webp"]: | |
| files.extend(glob.glob(os.path.join(od, ext, "**"), recursive=True)) | |
| files = [f for f in files if os.path.basename(f) not in [os.path.basename(ts), os.path.basename(tt)]] | |
| if not files: | |
| logger.warning("⚠️ No output — returning original") | |
| with open(tt, 'rb') as f: | |
| return Response(content=f.read(), media_type="image/png") | |
| with open(files[0], 'rb') as f: | |
| data = f.read() | |
| logger.info(f"✅ FaceFusion done: {len(data)} bytes") | |
| return Response(content=data, media_type="image/png") | |
| except Exception as e: | |
| logger.error(f"💥 {e}", exc_info=True) | |
| # 🔥 FALLBACK в случае любой ошибки | |
| try: | |
| if tt and os.path.exists(tt): | |
| with open(tt, 'rb') as f: | |
| return Response(content=f.read(), media_type="image/png") | |
| except: pass | |
| raise HTTPException(500, f"❌ {str(e)[:200]}") | |
| finally: | |
| for p in [ts, tt]: | |
| if p and os.path.exists(p): | |
| try: os.remove(p) | |
| except: pass | |
| if od and os.path.exists(od): | |
| try: shutil.rmtree(od) | |
| except: pass | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=8081) |