Spaces:
Running on T4
Running on T4
Update app.py
Browse files
app.py
CHANGED
|
@@ -51,12 +51,20 @@ DO_SPACES_KEY = os.getenv("DO_SPACES_KEY")
|
|
| 51 |
DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
|
| 52 |
DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
|
| 53 |
|
| 54 |
-
# NEW admin DB
|
| 55 |
ADMIN_MONGO_URL = os.getenv("ADMIN_MONGO_URL")
|
| 56 |
-
admin_client =
|
| 57 |
-
admin_db =
|
| 58 |
-
subcategories_col =
|
| 59 |
-
media_clicks_col =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
# OLD logs DB
|
| 62 |
MONGODB_URL = os.getenv("MONGODB_URL")
|
|
@@ -65,67 +73,88 @@ database = None
|
|
| 65 |
|
| 66 |
# --------------------- Download Models ---------------------
|
| 67 |
def download_models():
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
filename="models/inswapper_128.onnx",
|
| 72 |
-
repo_type="model",
|
| 73 |
-
local_dir=MODELS_DIR,
|
| 74 |
-
token=HF_TOKEN
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
buffalo_files = ["1k3d68.onnx", "2d106det.onnx", "genderage.onnx", "det_10g.onnx", "w600k_r50.onnx"]
|
| 78 |
-
for f in buffalo_files:
|
| 79 |
-
hf_hub_download(
|
| 80 |
repo_id=REPO_ID,
|
| 81 |
-
filename=
|
| 82 |
repo_type="model",
|
| 83 |
local_dir=MODELS_DIR,
|
| 84 |
token=HF_TOKEN
|
| 85 |
)
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
# --------------------- CodeFormer ---------------------
|
| 100 |
CODEFORMER_PATH = "CodeFormer/inference_codeformer.py"
|
| 101 |
|
| 102 |
-
# def ensure_codeformer():
|
| 103 |
-
# if not os.path.exists("CodeFormer"):
|
| 104 |
-
# subprocess.run("git clone https://github.com/sczhou/CodeFormer.git", shell=True, check=True)
|
| 105 |
-
# subprocess.run("pip install -r CodeFormer/requirements.txt", shell=True, check=True)
|
| 106 |
-
# subprocess.run("python CodeFormer/basicsr/setup.py develop", shell=True, check=True)
|
| 107 |
-
# subprocess.run("python CodeFormer/scripts/download_pretrained_models.py facelib", shell=True, check=True)
|
| 108 |
-
# subprocess.run("python CodeFormer/scripts/download_pretrained_models.py CodeFormer", shell=True, check=True)
|
| 109 |
def ensure_codeformer():
|
| 110 |
-
if not os.path.exists("CodeFormer"):
|
| 111 |
-
subprocess.run("git clone https://github.com/sczhou/CodeFormer.git", shell=True, check=True)
|
| 112 |
-
subprocess.run("pip install -r CodeFormer/requirements.txt", shell=True, check=True)
|
| 113 |
-
|
| 114 |
-
# Always ensure BasicSR is installed from local directory
|
| 115 |
-
# This is needed for Hugging Face Spaces where BasicSR can't be installed from GitHub
|
| 116 |
-
if os.path.exists("CodeFormer/basicsr/setup.py"):
|
| 117 |
-
subprocess.run("python CodeFormer/basicsr/setup.py develop", shell=True, check=True)
|
| 118 |
-
|
| 119 |
-
# Install realesrgan after BasicSR is installed (realesrgan depends on BasicSR)
|
| 120 |
-
# This must be done after BasicSR installation to avoid PyPI install issues
|
| 121 |
try:
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
ensure_codeformer()
|
| 131 |
|
|
@@ -545,10 +574,18 @@ fastapi_app = FastAPI()
|
|
| 545 |
@fastapi_app.on_event("startup")
|
| 546 |
async def startup_db():
|
| 547 |
global client, database
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
|
| 553 |
@fastapi_app.on_event("shutdown")
|
| 554 |
async def shutdown_db():
|
|
@@ -660,6 +697,8 @@ def multi_face_swap(src_img, tgt_img):
|
|
| 660 |
|
| 661 |
for src_face, _ in pairs:
|
| 662 |
# 🔁 re-detect current target faces
|
|
|
|
|
|
|
| 663 |
current_faces = face_analysis_app.get(result_img)
|
| 664 |
current_faces = sorted(current_faces, key=face_sort_key)
|
| 665 |
|
|
@@ -670,6 +709,8 @@ def multi_face_swap(src_img, tgt_img):
|
|
| 670 |
|
| 671 |
target_face = candidates[0]
|
| 672 |
|
|
|
|
|
|
|
| 673 |
result_img = swapper.get(
|
| 674 |
result_img,
|
| 675 |
target_face,
|
|
@@ -696,10 +737,14 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir=None):
|
|
| 696 |
|
| 697 |
src_faces = face_analysis_app.get(src_bgr)
|
| 698 |
tgt_faces = face_analysis_app.get(tgt_bgr)
|
|
|
|
|
|
|
| 699 |
if not src_faces or not tgt_faces:
|
| 700 |
return None, None, "❌ Face not detected in one of the images"
|
| 701 |
|
| 702 |
swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
|
|
|
|
|
|
|
| 703 |
swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
|
| 704 |
if swapped_bgr is None:
|
| 705 |
return None, None, "❌ Face swap failed"
|
|
@@ -1590,9 +1635,13 @@ async def face_swap_api(
|
|
| 1590 |
with swap_lock:
|
| 1591 |
result_img = tgt_bgr.copy()
|
| 1592 |
for src_face, _ in pairs:
|
|
|
|
|
|
|
| 1593 |
current_faces = sorted(face_analysis_app.get(result_img), key=face_sort_key)
|
| 1594 |
candidates = [f for f in current_faces if f.gender == src_face.gender] or current_faces
|
| 1595 |
target_face = candidates[0]
|
|
|
|
|
|
|
| 1596 |
result_img = swapper.get(result_img, target_face, src_face, paste_back=True)
|
| 1597 |
|
| 1598 |
result_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
|
|
|
|
| 51 |
DO_SPACES_SECRET = os.getenv("DO_SPACES_SECRET")
|
| 52 |
DO_SPACES_BUCKET = os.getenv("DO_SPACES_BUCKET")
|
| 53 |
|
| 54 |
+
# NEW admin DB (with error handling for missing env vars)
|
| 55 |
ADMIN_MONGO_URL = os.getenv("ADMIN_MONGO_URL")
|
| 56 |
+
admin_client = None
|
| 57 |
+
admin_db = None
|
| 58 |
+
subcategories_col = None
|
| 59 |
+
media_clicks_col = None
|
| 60 |
+
if ADMIN_MONGO_URL:
|
| 61 |
+
try:
|
| 62 |
+
admin_client = AsyncIOMotorClient(ADMIN_MONGO_URL)
|
| 63 |
+
admin_db = admin_client.adminPanel
|
| 64 |
+
subcategories_col = admin_db.subcategories
|
| 65 |
+
media_clicks_col = admin_db.media_clicks
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.warning(f"MongoDB admin connection failed (optional): {e}")
|
| 68 |
|
| 69 |
# OLD logs DB
|
| 70 |
MONGODB_URL = os.getenv("MONGODB_URL")
|
|
|
|
| 73 |
|
| 74 |
# --------------------- Download Models ---------------------
|
| 75 |
def download_models():
|
| 76 |
+
try:
|
| 77 |
+
logger.info("Downloading models...")
|
| 78 |
+
inswapper_path = hf_hub_download(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
repo_id=REPO_ID,
|
| 80 |
+
filename="models/inswapper_128.onnx",
|
| 81 |
repo_type="model",
|
| 82 |
local_dir=MODELS_DIR,
|
| 83 |
token=HF_TOKEN
|
| 84 |
)
|
| 85 |
|
| 86 |
+
buffalo_files = ["1k3d68.onnx", "2d106det.onnx", "genderage.onnx", "det_10g.onnx", "w600k_r50.onnx"]
|
| 87 |
+
for f in buffalo_files:
|
| 88 |
+
hf_hub_download(
|
| 89 |
+
repo_id=REPO_ID,
|
| 90 |
+
filename=f"models/buffalo_l/" + f,
|
| 91 |
+
repo_type="model",
|
| 92 |
+
local_dir=MODELS_DIR,
|
| 93 |
+
token=HF_TOKEN
|
| 94 |
+
)
|
| 95 |
|
| 96 |
+
logger.info("Models downloaded successfully.")
|
| 97 |
+
return inswapper_path
|
| 98 |
+
except Exception as e:
|
| 99 |
+
logger.error(f"Model download failed: {e}")
|
| 100 |
+
raise
|
| 101 |
|
| 102 |
+
try:
|
| 103 |
+
inswapper_path = download_models()
|
| 104 |
+
|
| 105 |
+
# --------------------- Face Analysis + Swapper ---------------------
|
| 106 |
+
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
| 107 |
+
face_analysis_app = FaceAnalysis(name="buffalo_l", root=MODELS_DIR, providers=providers)
|
| 108 |
+
face_analysis_app.prepare(ctx_id=0, det_size=(640, 640))
|
| 109 |
+
swapper = insightface.model_zoo.get_model(inswapper_path, providers=providers)
|
| 110 |
+
logger.info("Face analysis models loaded successfully")
|
| 111 |
+
except Exception as e:
|
| 112 |
+
logger.error(f"Failed to initialize face analysis models: {e}")
|
| 113 |
+
# Set defaults to prevent crash
|
| 114 |
+
inswapper_path = None
|
| 115 |
+
face_analysis_app = None
|
| 116 |
+
swapper = None
|
| 117 |
|
| 118 |
# --------------------- CodeFormer ---------------------
|
| 119 |
CODEFORMER_PATH = "CodeFormer/inference_codeformer.py"
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
def ensure_codeformer():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
try:
|
| 123 |
+
if not os.path.exists("CodeFormer"):
|
| 124 |
+
logger.info("CodeFormer not found, cloning repository...")
|
| 125 |
+
subprocess.run("git clone https://github.com/sczhou/CodeFormer.git", shell=True, check=True)
|
| 126 |
+
subprocess.run("pip install -r CodeFormer/requirements.txt", shell=True, check=False) # Non-critical deps
|
| 127 |
+
|
| 128 |
+
# Always ensure BasicSR is installed from local directory
|
| 129 |
+
# This is needed for Hugging Face Spaces where BasicSR can't be installed from GitHub
|
| 130 |
+
if os.path.exists("CodeFormer/basicsr/setup.py"):
|
| 131 |
+
logger.info("Installing BasicSR from local directory...")
|
| 132 |
+
subprocess.run("python CodeFormer/basicsr/setup.py develop", shell=True, check=True)
|
| 133 |
+
logger.info("BasicSR installed successfully")
|
| 134 |
+
|
| 135 |
+
# Install realesrgan after BasicSR is installed (realesrgan depends on BasicSR)
|
| 136 |
+
# This must be done after BasicSR installation to avoid PyPI install issues
|
| 137 |
+
try:
|
| 138 |
+
import realesrgan
|
| 139 |
+
logger.info("RealESRGAN already installed")
|
| 140 |
+
except ImportError:
|
| 141 |
+
logger.info("Installing RealESRGAN...")
|
| 142 |
+
subprocess.run("pip install --no-cache-dir realesrgan", shell=True, check=True)
|
| 143 |
+
logger.info("RealESRGAN installed successfully")
|
| 144 |
+
|
| 145 |
+
# Download models if CodeFormer exists (fixed logic)
|
| 146 |
+
if os.path.exists("CodeFormer"):
|
| 147 |
+
try:
|
| 148 |
+
subprocess.run("python CodeFormer/scripts/download_pretrained_models.py facelib", shell=True, check=False, timeout=300)
|
| 149 |
+
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
| 150 |
+
logger.warning("Failed to download facelib models (optional)")
|
| 151 |
+
try:
|
| 152 |
+
subprocess.run("python CodeFormer/scripts/download_pretrained_models.py CodeFormer", shell=True, check=False, timeout=300)
|
| 153 |
+
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
| 154 |
+
logger.warning("Failed to download CodeFormer models (optional)")
|
| 155 |
+
except Exception as e:
|
| 156 |
+
logger.error(f"CodeFormer setup failed: {e}")
|
| 157 |
+
logger.warning("Continuing without CodeFormer features...")
|
| 158 |
|
| 159 |
ensure_codeformer()
|
| 160 |
|
|
|
|
| 574 |
@fastapi_app.on_event("startup")
|
| 575 |
async def startup_db():
|
| 576 |
global client, database
|
| 577 |
+
if MONGODB_URL:
|
| 578 |
+
try:
|
| 579 |
+
logger.info("Initializing MongoDB for API logs...")
|
| 580 |
+
client = AsyncIOMotorClient(MONGODB_URL)
|
| 581 |
+
database = client.FaceSwap
|
| 582 |
+
logger.info("MongoDB initialized for API logs")
|
| 583 |
+
except Exception as e:
|
| 584 |
+
logger.warning(f"MongoDB connection failed (optional): {e}")
|
| 585 |
+
client = None
|
| 586 |
+
database = None
|
| 587 |
+
else:
|
| 588 |
+
logger.warning("MONGODB_URL not set, skipping MongoDB initialization")
|
| 589 |
|
| 590 |
@fastapi_app.on_event("shutdown")
|
| 591 |
async def shutdown_db():
|
|
|
|
| 697 |
|
| 698 |
for src_face, _ in pairs:
|
| 699 |
# 🔁 re-detect current target faces
|
| 700 |
+
if face_analysis_app is None:
|
| 701 |
+
raise ValueError("Face analysis models not initialized. Please ensure models are downloaded.")
|
| 702 |
current_faces = face_analysis_app.get(result_img)
|
| 703 |
current_faces = sorted(current_faces, key=face_sort_key)
|
| 704 |
|
|
|
|
| 709 |
|
| 710 |
target_face = candidates[0]
|
| 711 |
|
| 712 |
+
if swapper is None:
|
| 713 |
+
raise ValueError("Face swap models not initialized. Please ensure models are downloaded.")
|
| 714 |
result_img = swapper.get(
|
| 715 |
result_img,
|
| 716 |
target_face,
|
|
|
|
| 737 |
|
| 738 |
src_faces = face_analysis_app.get(src_bgr)
|
| 739 |
tgt_faces = face_analysis_app.get(tgt_bgr)
|
| 740 |
+
if face_analysis_app is None:
|
| 741 |
+
return None, None, "❌ Face analysis models not initialized. Please ensure models are downloaded."
|
| 742 |
if not src_faces or not tgt_faces:
|
| 743 |
return None, None, "❌ Face not detected in one of the images"
|
| 744 |
|
| 745 |
swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
|
| 746 |
+
if swapper is None:
|
| 747 |
+
return None, None, "❌ Face swap models not initialized. Please ensure models are downloaded."
|
| 748 |
swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
|
| 749 |
if swapped_bgr is None:
|
| 750 |
return None, None, "❌ Face swap failed"
|
|
|
|
| 1635 |
with swap_lock:
|
| 1636 |
result_img = tgt_bgr.copy()
|
| 1637 |
for src_face, _ in pairs:
|
| 1638 |
+
if face_analysis_app is None:
|
| 1639 |
+
raise HTTPException(status_code=500, detail="Face analysis models not initialized. Please ensure models are downloaded.")
|
| 1640 |
current_faces = sorted(face_analysis_app.get(result_img), key=face_sort_key)
|
| 1641 |
candidates = [f for f in current_faces if f.gender == src_face.gender] or current_faces
|
| 1642 |
target_face = candidates[0]
|
| 1643 |
+
if swapper is None:
|
| 1644 |
+
raise HTTPException(status_code=500, detail="Face swap models not initialized. Please ensure models are downloaded.")
|
| 1645 |
result_img = swapper.get(result_img, target_face, src_face, paste_back=True)
|
| 1646 |
|
| 1647 |
result_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
|