Spaces:
Sleeping
Sleeping
File size: 10,453 Bytes
2648814 492b161 9a200aa 2648814 9a200aa 2648814 2c6406f 2648814 492b161 9a200aa 492b161 2c6406f 2648814 2c6406f 492b161 2648814 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | """
model_bootstrap.py — Provisionnement automatique des modèles au démarrage.
Si les modèles ONNX ne sont pas présents localement, ils sont récupérés depuis
HuggingFace (via `huggingface_hub.hf_hub_download`, authentifié par `HF_TOKEN`)
puis préparés, **en arrière-plan** (thread démon) pour ne pas bloquer le serveur.
1. Détecteur IA global -> models/ai_detector.onnx (+ sidecar ai_detector.json)
Source : SDXL_MODEL_REPO (défaut `umm-maybe/AI-image-detector`).
- Si SDXL_MODEL_FILE est défini : téléchargement direct de l'ONNX publié.
- Sinon : téléchargement de pytorch_model.bin + config + preprocessor, puis
conversion PyTorch -> ONNX (transformers/torch) et écriture du sidecar.
2. Modèle visage -> models/deepfake_efficientnet.onnx
Source : ONNX_MODEL_REPO + ONNX_MODEL_FILE (repo HF + fichier .onnx).
Le modèle VIDÉO (ResNext50+LSTM, Naman712) n'est PLUS provisionné ici : il est
téléchargé ET chargé PARESSEUSEMENT par video_model.VideoDeepfakeAnalyzer, au
premier appel à predict() (première analyse vidéo). Voir video_model.py pour
VIDEO_MODEL_REPO / VIDEO_MODEL_FILE / VIDEO_MODEL_PATH / VIDEO_FAKE_INDEX.
Variables d'environnement :
HF_TOKEN jeton HuggingFace (repos privés / gated / quotas)
SDXL_MODEL_PATH chemin .onnx local explicite (court-circuite le DL)
SDXL_MODEL_REPO repo HF du détecteur IA (défaut umm-maybe/AI-image-detector)
SDXL_MODEL_FILE nom d'un .onnx déjà publié dans le repo (téléchargement direct)
ONNX_MODEL_PATH chemin .onnx visage local explicite
ONNX_MODEL_REPO repo HF du modèle visage
ONNX_MODEL_FILE fichier .onnx visage dans le repo
"""
import os
import json
import shutil
import threading
_BASE = os.path.dirname(os.path.abspath(__file__))
_MODELS = os.path.join(_BASE, "models")
AI_ONNX = os.path.join(_MODELS, "ai_detector.onnx")
AI_SIDECAR = os.path.join(_MODELS, "ai_detector.json")
FACE_ONNX = os.path.join(_MODELS, "deepfake_efficientnet.onnx")
YUNET_ONNX = os.path.join(_MODELS, "face_detector_yunet.onnx")
YUNET_URL = os.getenv(
"FACE_DETECTOR_URL",
"https://huggingface.co/opencv/face_detection_yunet/resolve/main/face_detection_yunet_2023mar.onnx")
_DEF_MEAN = [0.485, 0.456, 0.406]
_DEF_STD = [0.229, 0.224, 0.225]
def _log(msg):
print(f"[bootstrap] {msg}", flush=True)
def _hf_token():
return os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN") or None
# ── 1. Détecteur IA global ──────────────────────────────────────────────────
def ensure_sdxl_model():
if os.path.exists(AI_ONNX):
_log("ai_detector.onnx déjà présent — rien à télécharger.")
return
explicit = os.getenv("SDXL_MODEL_PATH")
if explicit and os.path.exists(explicit):
_log(f"SDXL_MODEL_PATH fourni ({explicit}) — pas de téléchargement.")
return
from huggingface_hub import hf_hub_download
repo = os.getenv("SDXL_MODEL_REPO", "umm-maybe/AI-image-detector")
token = _hf_token()
os.makedirs(_MODELS, exist_ok=True)
# (a) ONNX déjà publié dans le repo -> téléchargement direct
onnx_file = os.getenv("SDXL_MODEL_FILE")
if onnx_file:
try:
f = hf_hub_download(repo_id=repo, filename=onnx_file, token=token)
shutil.copy(f, AI_ONNX)
try:
s = hf_hub_download(repo_id=repo, filename="ai_detector.json", token=token)
shutil.copy(s, AI_SIDECAR)
except Exception:
pass
_log(f"ai_detector.onnx téléchargé directement depuis {repo}/{onnx_file}")
return
except Exception as e:
_log(f"ONNX direct indisponible ({e}) -> conversion PyTorch")
# (b) Téléchargement des fichiers source puis conversion PyTorch -> ONNX
src = os.path.join(_MODELS, "_ai_src")
os.makedirs(src, exist_ok=True)
hf_hub_download(repo_id=repo, filename="config.json", token=token, local_dir=src)
try:
hf_hub_download(repo_id=repo, filename="preprocessor_config.json", token=token, local_dir=src)
except Exception:
_log("preprocessor_config.json absent — valeurs ImageNet par défaut.")
try:
hf_hub_download(repo_id=repo, filename="pytorch_model.bin", token=token, local_dir=src)
except Exception:
hf_hub_download(repo_id=repo, filename="model.safetensors", token=token, local_dir=src)
_log(f"poids récupérés depuis {repo} -> conversion ONNX en cours…")
_convert_to_onnx(src)
def _convert_to_onnx(src_dir):
import numpy as np
import torch
import torch.nn as nn
from transformers import AutoModelForImageClassification
model, info = AutoModelForImageClassification.from_pretrained(
src_dir, output_loading_info=True, attn_implementation="eager")
miss = [k for k in info.get("missing_keys", [])
if "relative_position_index" not in k and "attn_mask" not in k]
if miss:
raise RuntimeError(f"poids manquants après chargement: {miss[:8]}")
model.eval()
class Wrap(nn.Module):
def __init__(self, m):
super().__init__()
self.m = m
def forward(self, x):
return self.m(pixel_values=x).logits
size = int(getattr(model.config, "image_size", 224) or 224)
dummy = torch.randn(1, 3, size, size)
torch.onnx.export(
Wrap(model).eval(), dummy, AI_ONNX,
input_names=["pixel_values"], output_names=["logits"],
opset_version=17, do_constant_folding=True, dynamo=False)
# Sidecar : labels, taille, normalisation, index « fake »
id2label = {int(k): v for k, v in (model.config.id2label or {0: "fake", 1: "real"}).items()}
def is_fake(name):
n = str(name).lower()
return any(t in n for t in ("artificial", "ai", "fake", "synth", "generated", "diffusion", "midjourney"))
env_idx = os.getenv("SDXL_FAKE_INDEX")
if env_idx not in (None, ""):
fake_index = int(env_idx)
else:
cands = [i for i, n in id2label.items() if is_fake(n)]
fake_index = cands[0] if cands else 0
mean, std, psize = list(_DEF_MEAN), list(_DEF_STD), size
pp = os.path.join(src_dir, "preprocessor_config.json")
if os.path.exists(pp):
try:
d = json.load(open(pp, encoding="utf-8"))
mean = [float(x) for x in d.get("image_mean", mean)]
std = [float(x) for x in d.get("image_std", std)]
s = d.get("size", size)
if isinstance(s, dict):
psize = int(s.get("height") or s.get("shortest_edge") or size)
else:
psize = int(s)
except Exception:
pass
sidecar = {"size": int(psize), "mean": mean, "std": std,
"labels": {str(k): v for k, v in id2label.items()},
"fake_index": int(fake_index)}
with open(AI_SIDECAR, "w", encoding="utf-8") as f:
json.dump(sidecar, f, ensure_ascii=False, indent=2)
_log(f"ai_detector.onnx prêt (labels={id2label}, fake_index={fake_index}, size={psize}).")
# ── 2. Modèle visage (EfficientNet / FaceForensics++) ───────────────────────
def ensure_face_model():
if os.path.exists(FACE_ONNX):
_log("deepfake_efficientnet.onnx déjà présent — rien à télécharger.")
return
explicit = os.getenv("ONNX_MODEL_PATH")
if explicit and os.path.exists(explicit):
_log(f"ONNX_MODEL_PATH fourni ({explicit}) — pas de téléchargement.")
return
repo = os.getenv("ONNX_MODEL_REPO")
fname = os.getenv("ONNX_MODEL_FILE")
if not repo or not fname:
_log("modèle visage non téléchargé : définir ONNX_MODEL_REPO + ONNX_MODEL_FILE "
"(repo HF + fichier .onnx). L'app fonctionne sans (analyse visage désactivée).")
return
from huggingface_hub import hf_hub_download
os.makedirs(_MODELS, exist_ok=True)
f = hf_hub_download(repo_id=repo, filename=fname, token=_hf_token())
shutil.copy(f, FACE_ONNX)
_log(f"deepfake_efficientnet.onnx téléchargé depuis {repo}/{fname}")
# ── Modèle vidéo ResNext50+LSTM ─────────────────────────────────────────────
# Téléchargé ET chargé PARESSEUSEMENT par video_model.VideoDeepfakeAnalyzer, au
# tout premier appel à predict() (première analyse vidéo) — plus au démarrage.
# ── 3. Détecteur de visages YuNet (famille RetinaFace, ~230 Ko) ─────────────
def ensure_yunet():
if os.path.exists(YUNET_ONNX):
_log("face_detector_yunet.onnx déjà présent — rien à télécharger.")
return
import urllib.request
os.makedirs(_MODELS, exist_ok=True)
tmp = YUNET_ONNX + ".part"
urllib.request.urlretrieve(YUNET_URL, tmp)
if os.path.getsize(tmp) < 100_000: # un pointeur LFS/HTML fait < 100 Ko
os.unlink(tmp)
raise RuntimeError("téléchargement YuNet invalide (fichier trop petit)")
os.replace(tmp, YUNET_ONNX)
_log("face_detector_yunet.onnx téléchargé (YuNet).")
# ── Orchestration arrière-plan ──────────────────────────────────────────────
def _run(detector):
for label, fn in (("détecteur de visages", ensure_yunet),
("IA global", ensure_sdxl_model), ("visage", ensure_face_model)):
try:
fn()
except Exception as e:
_log(f"échec modèle {label} : {e}")
# Les analyseurs chargent leur ONNX paresseusement : comme le fichier était
# absent au démarrage, `_tried` est resté None et le prochain `predict()`
# rechargera automatiquement le modèle fraîchement téléchargé. Rien à forcer.
_log("provisionnement terminé.")
def start_background_bootstrap(detector=None):
"""Lance le provisionnement des modèles dans un thread démon (non bloquant)."""
if os.getenv("DISABLE_MODEL_BOOTSTRAP", "0").lower() in ("1", "true", "yes"):
_log("désactivé (DISABLE_MODEL_BOOTSTRAP).")
return
threading.Thread(target=_run, args=(detector,), name="model-bootstrap", daemon=True).start()
_log("démarré (arrière-plan).")
|