FalconVQA Docs
Getting Started

Installation

The full local stack — core, Supabase, storage, and the Next.js app — with the reasoning behind each piece.

Requirements

core needs Python 3.13, a CUDA GPU, and an OpenAI API key. It was developed against Python 3.13.6, torch 2.11.0+cu130 and an RTX 4060 (8 GB) — a newer combination than most guides assume, and one that works. Do not downgrade to "known good" versions without a measured reason.

frontend needs Node 20+ and a Supabase project.

Everything except the vision-language calls and answer synthesis runs locally: BGE embeddings, Qdrant (embedded), Whisper, pyannote, Silero VAD, PySceneDetect, CLIP, YOLO and EasyOCR.

Repository layout

serve.py

core/data/ holds every piece of runtime state — records, vectors, the video cache, downloaded model weights, and uploads. Deleting it is a full reset and costs nothing but re-work.

1. core

Install

cd core
pip install torch==2.11.0 torchvision==0.26.0 torchaudio==2.11.0 \
    --index-url https://download.pytorch.org/whl/cu130
pip install -r requirements.txt

uv works too — uv sync then uv run python serve.py.

Environment

core/.env
OPENAI_API_KEY=sk-...
HF_TOKEN=hf_...
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...

HF_TOKEN is only needed for diarization, and its model is gated — accept the terms at pyannote/speaker-diarization-community-1 first, or the analyzer will fail at load time.

Storage bucket

Video bytes live in a public Supabase Storage bucket named videos, created once per project:

from supabase import create_client
create_client(URL, SERVICE_ROLE_KEY).storage.create_bucket("videos", options={"public": True})

GET /health reports status: "degraded" and storage.ok: false when the key is wrong or the bucket is missing. Worth checking there rather than discovering it minutes into a background job.

Videos are URLs outside, paths inside. Every request and response speaks Storage URLs; everything below storage.py gets a local path and does not know Storage exists. A video is cached once under its content hash and every analysis pass runs against that local copy — decoding straight from a URL would pay the network for what is meant to be a local seek.

Run

python serve.py                    # API + web UI on http://127.0.0.1:8077
python serve.py --api-only         # API only, no UI
python serve.py --port 9000
python serve.py --reload           # auto-restart on code changes
python serve.py --host 0.0.0.0     # reachable from the LAN

Equivalent, invoking uvicorn directly:

python -m uvicorn videomind.api.app:app --port 8077
VIDEOMIND_UI=0 python -m uvicorn videomind.api.app:app --port 8077   # API only

Use --api-only when the built-in UI is not wanted — behind another frontend, or for an MCP client that has no use for HTML. It drops the / route and nothing else.

2. Database

Run frontend/lib/supabase/migrations/schema.sql in the Supabase SQL editor. It creates projects, conversations, messages, video_core, the project-assets bucket, update_updated_at(), and the RLS policies.

The older videos table (from the VideoDB era) is left in place and is no longer read by anything.

The video_core table

This is the join between the application's world and core's.

ColumnPurpose
project_id, user_idOwnership. RLS restricts every row to auth.uid() = user_id
title, source_type, storage_path, source_urlWhere the video came from
core_video_idCore's identity — sha1(bytes)[:16]. Null until core has downloaded the file
playback_url, poster_url, duration, size_bytesWritten back from the ingest job result
status, job_id, stage, progress, errorIngest lifecycle, reconciled on every poll
ingest_configWhat the pipeline was asked for, replayed verbatim on re-index
analyzers, aggregates, chunk_config, chunk_countWhat it actually produced

core_video_id is unique per project, never globally: the same file uploaded to two projects is one video in core. Deleting one project's row must not delete core's video while another row still points at it — the delete route counts references first.

3. frontend

cd frontend
npm install
cp env.example .env.local
npm run dev

The full variable list is in Configuration. At minimum you need the three Supabase values, one model provider key, and CORE_API_URL.

Verifying the install

curl http://127.0.0.1:8077/health
{
  "status": "ok",
  "ui": true,
  "storage": { "ok": true, "bucket": "videos", "error": null },
  "analyzers": ["default_video", "diarization", "object_detection", "ocr", "people", "transcript"],
  "aggregators": ["chapters", "cooccurrence", "entities", "entity_timelines", "events",
                  "ner", "novelty", "object_entities", "sentiment", "speaker_stats",
                  "stats", "summary"]
}

Then, from the frontend, sign in and open /projects. If the upload dialog's analyzer list populates, the frontend can reach core — that list is built from GET /api/core/capabilities, which proxies core's /analyzers and /schema.

Resetting

Everything core writes lives under core/data/:

rm -rf core/data

Application rows survive in Postgres, so after a core reset the videos will show as failed with a message telling you to re-index. That is the intended recovery path.

On this page