from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException from fastapi.responses import FileResponse from sqlalchemy.orm import Session from pydantic import BaseModel from ..duplicates_manager import DuplicatesManager from ..config import settings, Settings from ..database import get_db, SessionLocal from ..samba_manager import SambaManager from .. import models import os import json from typing import List router = APIRouter() class ScanConfig(BaseModel): paths: List[str] = ["/videos"] algorithm: str = "phash" scan_type: str = "fast" # fast or scene class DeleteRequest(BaseModel): ids: List[int] class ExclusionRequest(BaseModel): path: str def run_scan_videos_background(paths, algorithm, scan_type): db = SessionLocal() app_settings = Settings() manager = DuplicatesManager(app_settings, db) samba_manager = SambaManager( app_settings.samba_server_ip, "isolation", app_settings.samba_username, app_settings.samba_password, ) try: manager.scan_videos(samba_manager, paths, algorithm, scan_type) finally: samba_manager.close() db.close() @router.post("/scan/start") def start_scan(config: ScanConfig, background_tasks: BackgroundTasks): background_tasks.add_task(run_scan_videos_background, config.paths, config.algorithm, config.scan_type) return {"message": "Scan started"} @router.post("/scan/cancel") def cancel_scan(): try: os.makedirs("logs", exist_ok=True) with open("logs/duplicates_scan.cancel", 'w') as f: f.write("cancel") except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to cancel scan: {e}") return {"message": "Cancellation requested"} @router.get("/scan/log") def get_scan_log(limit: int = 100): log_file = "logs/duplicates_scan.log" if not os.path.exists(log_file): return {"lines": []} try: # Simple read and tail (not efficient for huge files but fine here) with open(log_file, "r", encoding="utf-8", errors="replace") as f: lines = f.readlines() return {"lines": lines[-limit:]} except Exception as e: return {"lines": [f"Error reading log: {e}"]} @router.get("/scan/progress") def get_scan_progress(): path = "logs/duplicates_scan.json" if os.path.exists(path): try: with open(path, 'r') as f: return json.load(f) except: return {"status": "error", "message": "Read failed"} return {"status": "idle"} @router.get("/exclusions") def get_exclusions(db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) return manager.exclusions @router.post("/exclusions") def add_exclusion(req: ExclusionRequest, db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) manager.add_exclusion(req.path) return {"message": "Added"} @router.delete("/exclusions") def remove_exclusion(req: ExclusionRequest, db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) manager.remove_exclusion(req.path) return {"message": "Removed"} @router.post("/find") def find_duplicates(threshold: float = 0.95, method: str = "fast", db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) return manager.find_duplicates(threshold, method) @router.get("/reports") def get_reports(db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) return manager.get_reports() @router.get("/report/{report_id}") def get_report(report_id: int, db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) res = manager.get_report(report_id) if not res: raise HTTPException(status_code=404) return res @router.delete("/files") def delete_files(request: DeleteRequest, db: Session = Depends(get_db)): manager = DuplicatesManager(settings, db) deleted = manager.delete_files(request.ids) return {"deleted_ids": deleted} @router.get("/thumbnails/{video_id}") def get_thumbnail(video_id: int, db: Session = Depends(get_db)): video = db.query(models.VideoFile).get(video_id) if not video or not video.contact_sheet_path: raise HTTPException(status_code=404, detail="Thumbnail not found") path = os.path.join("resources/cache/thumbnails", video.contact_sheet_path) if not os.path.exists(path): raise HTTPException(status_code=404, detail="Thumbnail file missing") return FileResponse(path)