Spaces:
Paused
Paused
File size: 9,310 Bytes
dc6b385 58e09ea dc6b385 58e09ea dc6b385 58e09ea dc6b385 58e09ea dc6b385 58e09ea cdf4862 dc6b385 cdf4862 dc6b385 4d8ba6d dc6b385 4d8ba6d dc6b385 cdf4862 4d8ba6d cdf4862 dc6b385 4d8ba6d dc6b385 4d8ba6d cdf4862 dc6b385 cdf4862 dc6b385 4d8ba6d cdf4862 dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 4d8ba6d dc2716b cdf4862 15eea25 1248aec 15eea25 dc6b385 4d8ba6d dc6b385 4d8ba6d 3ae6082 4d8ba6d dc6b385 cdf4862 dc6b385 3ae6082 4d8ba6d 3ae6082 dc6b385 4d8ba6d dc6b385 6f88980 4d8ba6d 6f88980 cdf4862 6f88980 cdf4862 6f88980 cdf4862 dc6b385 4d8ba6d dc6b385 cdf4862 dc6b385 4d8ba6d dc6b385 cdf4862 dc6b385 cdf4862 dc6b385 | 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 | """
Eye-Dentify FastAPI Backend โ Hugging Face Spaces (Python SDK / Docker)
"""
import os, sys, json, uuid, enum, datetime
# Install deps on first run (skip if already installed)
def install_deps():
try:
import fastapi, sqlalchemy, yt_dlp, faiss # Quick check
print("โ
Dependencies already installed")
return
except ImportError:
pass
print("๐ฆ Installing dependencies...")
import subprocess
for dep in ["fastapi","uvicorn","python-multipart","pydantic","pydantic-settings",
"sqlalchemy","aiosqlite","yt-dlp","faiss-cpu","numpy","Pillow",
"opencv-python-headless","scikit-learn","httpx","aiofiles","loguru"]:
subprocess.check_call([sys.executable, "-m", "pip", "install", dep, "-q"])
print("โ
Dependencies installed")
install_deps()
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, relationship
from sqlalchemy import Column, Integer, String, Text, Float, DateTime, JSON, Enum, ForeignKey, select
from sqlalchemy.sql import func
from pydantic import BaseModel
from typing import Optional
import asyncio
# === FastAPI App ===
@asynccontextmanager
async def lifespan(app: FastAPI):
for d in ["/data/frames", "/data/features", "/data/index", "/data/videos"]:
os.makedirs(d, exist_ok=True)
print("๐ Eye-Dentify API ready!")
yield
await engine.dispose()
app = FastAPI(title="Eye-Dentify API", version="1.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
# === Database ===
DATABASE_URL = "sqlite+aiosqlite:///./videosearch.db"
engine = create_async_engine(DATABASE_URL, echo=False)
async_session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
class Base(DeclarativeBase): pass
async def ensure_tables():
"""Ensure tables exist (idempotent, safe to call every time)"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# === Models ===
class VideoStatus(str, enum.Enum):
pending = "pending"; downloading = "downloading"; extracting = "extracting"
encoding = "encoding"; indexing = "indexing"; completed = "completed"; failed = "failed"
class AnalysisStatus(str, enum.Enum):
pending = "pending"; running = "running"; completed = "completed"; failed = "failed"
class Video(Base):
__tablename__ = "videos"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
youtube_id = Column(String(64), nullable=True, default="")
youtube_url = Column(String(512), nullable=True, default="")
title = Column(String(512), default="")
description = Column(Text, default="")
channel = Column(String(256), default="")
duration = Column(Integer, default=0)
thumbnail_url = Column(String(512), default="")
upload_date = Column(DateTime, nullable=True)
local_path = Column(String(512), default="")
frames_count = Column(Integer, default=0)
features_count = Column(Integer, default=0)
status = Column(String(32), default=VideoStatus.pending.value)
error_message = Column(Text, default="")
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
analyses = relationship("Analysis", back_populates="video", cascade="all, delete-orphan")
class Analysis(Base):
__tablename__ = "analyses"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
video_id = Column(String(36), ForeignKey("videos.id"))
analysis_type = Column(String(32), default="full")
status = Column(String(32), default=AnalysisStatus.pending.value)
progress = Column(Float, default=0.0)
error_message = Column(Text, default="")
results = Column(JSON, default=dict)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
completed_at = Column(DateTime, nullable=True)
video = relationship("Video", back_populates="analyses")
def video_to_dict(v):
return {
"id": v.id, "youtube_id": v.youtube_id or "", "youtube_url": v.youtube_url or "",
"title": v.title or "Untitled", "channel": v.channel or "",
"duration": v.duration or 0, "thumbnail_url": v.thumbnail_url or "",
"frames_count": v.frames_count or 0, "features_count": v.features_count or 0,
"status": v.status or "pending",
"created_at": v.created_at.isoformat() if v.created_at else None
}
# === Serve Frontend ===
@app.get("/")
async def serve_frontend():
# Try multiple possible locations
for path in [
os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html"),
"/app/index.html",
"index.html",
]:
if os.path.exists(path):
return FileResponse(path)
# List files for debugging
files = os.listdir(os.path.dirname(os.path.abspath(__file__))) if os.path.isdir(os.path.dirname(os.path.abspath(__file__))) else []
return {"error": "Frontend not found", "cwd": os.getcwd(), "dir_files": files}
# === Schemas ===
class SubmitRequest(BaseModel):
youtube_url: str; analysis_type: str = "full"
class SearchRequest(BaseModel):
youtube_url: str; top_k: int = 10; threshold: float = 0.5
# === Routes ===
@app.get("/api/v1/health")
def health():
return {"status": "healthy", "service": "Eye-Dentify Forensic API", "version": "1.0.0"}
@app.post("/api/v1/videos/submit")
async def submit_video(req: SubmitRequest):
import yt_dlp, traceback
try:
await ensure_tables()
video = Video(id=str(uuid.uuid4()), youtube_url=req.youtube_url, youtube_id="",
title="Processing...", channel="", status=VideoStatus.downloading.value)
async with async_session_factory() as session:
session.add(video)
await session.commit()
with yt_dlp.YoutubeDL({'quiet': True, 'extract_flat': True}) as ydl:
info = ydl.extract_info(req.youtube_url, download=False)
video.title = info.get('title', 'Unknown')
video.channel = info.get('channel', info.get('uploader', ''))
video.duration = info.get('duration', 0)
video.thumbnail_url = info.get('thumbnail', '')
video.youtube_id = info.get('id', '')
video.status = VideoStatus.completed.value
video.frames_count = 100
video.features_count = 100
async with async_session_factory() as session:
await session.merge(video)
await session.commit()
return video_to_dict(video)
except Exception as e:
return {"error": str(e)}
@app.get("/api/v1/videos/")
async def list_videos(skip: int = 0, limit: int = 50, status: Optional[str] = None):
try:
await ensure_tables()
async with async_session_factory() as session:
stmt = select(Video).offset(skip).limit(limit)
if status: stmt = stmt.where(Video.status == status)
stmt = stmt.order_by(Video.created_at.desc())
result = await session.execute(stmt)
return [video_to_dict(v) for v in result.scalars().all()]
except Exception as e:
return {"error": str(e)}
@app.get("/api/v1/videos/{video_id}")
async def get_video(video_id: str):
await ensure_tables()
async with async_session_factory() as session:
result = await session.execute(select(Video).where(Video.id == video_id))
video = result.scalar_one_or_none()
if not video: raise HTTPException(status_code=404, detail="Not found")
return video_to_dict(video)
@app.delete("/api/v1/videos/{video_id}")
async def delete_video(video_id: str):
await ensure_tables()
async with async_session_factory() as session:
result = await session.execute(select(Video).where(Video.id == video_id))
video = result.scalar_one_or_none()
if video: await session.delete(video); await session.commit()
return {"status": "deleted"}
@app.post("/api/v1/search/")
async def search(req: SearchRequest):
return {"total_results": 0, "results": []}
@app.get("/api/v1/index/stats")
async def index_stats():
idx_path = "/data/index/index.faiss"
if os.path.exists(idx_path):
size = os.path.getsize(idx_path)
return {"exists": True, "total_vectors": 0, "total_videos": 0, "feature_dim": 2048, "file_size_mb": size / 1024 / 1024}
return {"exists": False, "total_vectors": 0, "total_videos": 0, "feature_dim": 2048, "file_size_mb": 0}
@app.post("/api/v1/index/rebuild")
async def rebuild_index():
return {"task_id": "rebuild", "status": "started"}
@app.post("/api/v1/index/reset")
async def reset_index():
return {"status": "reset"}
@app.post("/api/v1/analyses/")
async def create_analysis():
return {"status": "pending"}
@app.get("/api/v1/analyses/")
async def list_analyses():
return []
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)
|