Add runnable local CV model worker

This commit is contained in:
JSLMPR 2026-07-11 16:40:59 +02:00
parent 82e557905e
commit 9dc720b0cc
5 changed files with 54 additions and 13 deletions

View File

@ -41,15 +41,23 @@ VIDEO_EDITING_VISUAL_ANALYSIS_FALLBACK_TO_HEURISTIC=true
This repository includes an optional starter worker: This repository includes an optional starter worker:
```bash ```bash
python3 -m venv .venv-local-cv tools/run_local_cv_worker.sh
. .venv-local-cv/bin/activate ```
pip install fastapi uvicorn opencv-python
# Optional YOLO object detection: The starter worker installs `tools/local_cv_requirements.txt` into `.venv-local-cv`.
pip install ultralytics It uses OpenCV for blur, exposure, and face-presence signals. It uses YOLO through `ultralytics` for object detection.
export LOCAL_CV_YOLO_MODEL=yolov8n.pt
uvicorn tools.local_cv_worker:app --host 127.0.0.1 --port 8091 Default model behavior:
- `LOCAL_CV_YOLO_MODEL` defaults to `yolov8n.pt`.
- Ultralytics downloads/caches `yolov8n.pt` on first use if it is not already present.
- Set `LOCAL_CV_YOLO_MODEL=/absolute/path/to/model.pt` to use local weights.
- Set `LOCAL_CV_DISABLE_YOLO=true` to run only OpenCV-based analysis.
Health check:
```bash
curl http://127.0.0.1:8091/health
``` ```
## Worker Request Contract ## Worker Request Contract

View File

@ -49,7 +49,7 @@ video-clipping:
luts-folder: ${VIDEO_EDITING_ASSETS_LUTS_FOLDER:./input/highlights/assets/luts} luts-folder: ${VIDEO_EDITING_ASSETS_LUTS_FOLDER:./input/highlights/assets/luts}
voiceover-folder: ${VIDEO_EDITING_ASSETS_VOICEOVER_FOLDER:./output/highlight-projects/_voiceover-cache} voiceover-folder: ${VIDEO_EDITING_ASSETS_VOICEOVER_FOLDER:./output/highlight-projects/_voiceover-cache}
visual-analysis: visual-analysis:
provider: ${VIDEO_EDITING_VISUAL_ANALYSIS_PROVIDER:heuristic} provider: ${VIDEO_EDITING_VISUAL_ANALYSIS_PROVIDER:local-cv} #local-cv or heuristic
endpoint: ${VIDEO_EDITING_VISUAL_ANALYSIS_ENDPOINT:http://127.0.0.1:8091/v1/analyze-visuals} endpoint: ${VIDEO_EDITING_VISUAL_ANALYSIS_ENDPOINT:http://127.0.0.1:8091/v1/analyze-visuals}
timeout-ms: ${VIDEO_EDITING_VISUAL_ANALYSIS_TIMEOUT_MS:30000} timeout-ms: ${VIDEO_EDITING_VISUAL_ANALYSIS_TIMEOUT_MS:30000}
fallback-to-heuristic: ${VIDEO_EDITING_VISUAL_ANALYSIS_FALLBACK_TO_HEURISTIC:true} fallback-to-heuristic: ${VIDEO_EDITING_VISUAL_ANALYSIS_FALLBACK_TO_HEURISTIC:true}

View File

@ -0,0 +1,4 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
opencv-python==4.10.0.84
ultralytics==8.3.57

View File

@ -2,10 +2,8 @@
"""Optional local CV worker for the Spring visual-analysis provider. """Optional local CV worker for the Spring visual-analysis provider.
Run: Run:
pip install fastapi uvicorn opencv-python pip install -r tools/local_cv_requirements.txt
# Optional object detection: uvicorn tools.local_cv_worker:app --host 127.0.0.1 --port 8091
pip install ultralytics
LOCAL_CV_YOLO_MODEL=yolov8n.pt uvicorn tools.local_cv_worker:app --host 127.0.0.1 --port 8091
""" """
from __future__ import annotations from __future__ import annotations
@ -34,6 +32,16 @@ app = FastAPI(title="Local CV Visual Analysis Worker")
_yolo_model: Any | None = None _yolo_model: Any | None = None
@app.get("/health")
def health() -> dict[str, Any]:
return {
"status": "ok",
"opencvAvailable": cv2 is not None,
"yoloAvailable": YOLO is not None,
"yoloModel": configured_yolo_model(),
}
@app.post("/v1/analyze-visuals") @app.post("/v1/analyze-visuals")
def analyze_visuals(payload: dict[str, Any]) -> dict[str, Any]: def analyze_visuals(payload: dict[str, Any]) -> dict[str, Any]:
source = payload.get("source", {}) source = payload.get("source", {})
@ -101,7 +109,7 @@ def object_labels(thumbnails: list[str]) -> list[dict[str, Any]]:
def yolo_model() -> Any | None: def yolo_model() -> Any | None:
global _yolo_model global _yolo_model
model_path = os.getenv("LOCAL_CV_YOLO_MODEL") model_path = configured_yolo_model()
if YOLO is None or not model_path: if YOLO is None or not model_path:
return None return None
if _yolo_model is None: if _yolo_model is None:
@ -109,6 +117,12 @@ def yolo_model() -> Any | None:
return _yolo_model return _yolo_model
def configured_yolo_model() -> str:
if os.getenv("LOCAL_CV_DISABLE_YOLO", "false").lower() == "true":
return ""
return os.getenv("LOCAL_CV_YOLO_MODEL", "yolov8n.pt")
def face_presence(thumbnails: list[str]) -> str: def face_presence(thumbnails: list[str]) -> str:
if cv2 is None or not thumbnails: if cv2 is None or not thumbnails:
return "unknown_without_face_detector" return "unknown_without_face_detector"

15
tools/run_local_cv_worker.sh Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
HOST="${LOCAL_CV_HOST:-127.0.0.1}"
PORT="${LOCAL_CV_PORT:-8091}"
if [ ! -d ".venv-local-cv" ]; then
python3 -m venv .venv-local-cv
fi
. .venv-local-cv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r tools/local_cv_requirements.txt
exec uvicorn tools.local_cv_worker:app --host "$HOST" --port "$PORT"