From 9dc720b0cc1e762cd8d572f0969b3bf0759953ea Mon Sep 17 00:00:00 2001 From: JSLMPR Date: Sat, 11 Jul 2026 16:40:59 +0200 Subject: [PATCH] Add runnable local CV model worker --- docs/local-cv-visual-analysis-guide.md | 22 +++++++++++++++------- src/main/resources/application.yml | 2 +- tools/local_cv_requirements.txt | 4 ++++ tools/local_cv_worker.py | 24 +++++++++++++++++++----- tools/run_local_cv_worker.sh | 15 +++++++++++++++ 5 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 tools/local_cv_requirements.txt create mode 100755 tools/run_local_cv_worker.sh diff --git a/docs/local-cv-visual-analysis-guide.md b/docs/local-cv-visual-analysis-guide.md index a7c3dcc..6c16c25 100644 --- a/docs/local-cv-visual-analysis-guide.md +++ b/docs/local-cv-visual-analysis-guide.md @@ -41,15 +41,23 @@ VIDEO_EDITING_VISUAL_ANALYSIS_FALLBACK_TO_HEURISTIC=true This repository includes an optional starter worker: ```bash -python3 -m venv .venv-local-cv -. .venv-local-cv/bin/activate -pip install fastapi uvicorn opencv-python +tools/run_local_cv_worker.sh +``` -# Optional YOLO object detection: -pip install ultralytics -export LOCAL_CV_YOLO_MODEL=yolov8n.pt +The starter worker installs `tools/local_cv_requirements.txt` into `.venv-local-cv`. +It uses OpenCV for blur, exposure, and face-presence signals. It uses YOLO through `ultralytics` for object detection. -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 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ac5b0a4..3b7175e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,7 +49,7 @@ video-clipping: luts-folder: ${VIDEO_EDITING_ASSETS_LUTS_FOLDER:./input/highlights/assets/luts} voiceover-folder: ${VIDEO_EDITING_ASSETS_VOICEOVER_FOLDER:./output/highlight-projects/_voiceover-cache} 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} timeout-ms: ${VIDEO_EDITING_VISUAL_ANALYSIS_TIMEOUT_MS:30000} fallback-to-heuristic: ${VIDEO_EDITING_VISUAL_ANALYSIS_FALLBACK_TO_HEURISTIC:true} diff --git a/tools/local_cv_requirements.txt b/tools/local_cv_requirements.txt new file mode 100644 index 0000000..1e32f03 --- /dev/null +++ b/tools/local_cv_requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +opencv-python==4.10.0.84 +ultralytics==8.3.57 diff --git a/tools/local_cv_worker.py b/tools/local_cv_worker.py index 18fa39b..7c4a132 100644 --- a/tools/local_cv_worker.py +++ b/tools/local_cv_worker.py @@ -2,10 +2,8 @@ """Optional local CV worker for the Spring visual-analysis provider. Run: - pip install fastapi uvicorn opencv-python - # Optional object detection: - pip install ultralytics - LOCAL_CV_YOLO_MODEL=yolov8n.pt uvicorn tools.local_cv_worker:app --host 127.0.0.1 --port 8091 + pip install -r tools/local_cv_requirements.txt + uvicorn tools.local_cv_worker:app --host 127.0.0.1 --port 8091 """ from __future__ import annotations @@ -34,6 +32,16 @@ app = FastAPI(title="Local CV Visual Analysis Worker") _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") def analyze_visuals(payload: dict[str, Any]) -> dict[str, Any]: source = payload.get("source", {}) @@ -101,7 +109,7 @@ def object_labels(thumbnails: list[str]) -> list[dict[str, Any]]: def yolo_model() -> Any | None: global _yolo_model - model_path = os.getenv("LOCAL_CV_YOLO_MODEL") + model_path = configured_yolo_model() if YOLO is None or not model_path: return None if _yolo_model is None: @@ -109,6 +117,12 @@ def yolo_model() -> Any | None: 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: if cv2 is None or not thumbnails: return "unknown_without_face_detector" diff --git a/tools/run_local_cv_worker.sh b/tools/run_local_cv_worker.sh new file mode 100755 index 0000000..689fde0 --- /dev/null +++ b/tools/run_local_cv_worker.sh @@ -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"