Skip to content

Repository files navigation

Annotator Hub

A small internal web app for uploading videos, uploading matching JSON person-tracking annotations, and browsing coverage stats/filters across the library.

Storage is an S3-compatible object store (MinIO). The bucket is the database: the app browses the existing processed/<device>/<clip>/ clips (each with a *_detections_visualized.mp4, a metadata.json, and — where present — a matching person/station annotation JSON) and lets you upload new videos into an app-uploads/ prefix. Per-video technical metadata (duration/resolution/fps) is probed lazily from the object and cached to a small _appmeta.json sidecar so large videos aren't re-probed on every page load. Videos are streamed to the browser through the app (with HTTP Range support for seeking) — clients never talk to the object store directly.

Setup

pip install -r requirements.txt

Run

python app.py

This starts the server on 0.0.0.0:5000, so it's reachable from other machines on the same network at http://<this-machine's-hostname-or-IP>:5000/ — e.g. http://hu-rohadutt-hyd:5000/. Share that URL with the USA colleague uploading videos.

The Flask dev server is fine for a couple of internal users. If you want a more robust server (e.g. it should survive terminal closing, or handle concurrent large uploads more gracefully), run it via waitress instead:

waitress-serve --host=0.0.0.0 --port=5000 app:app

Configuration

The app talks to the object store over its S3 API. All connection settings are loaded from a .env file in the project root (via python-dotenv). Copy the template and fill in the credentials before first run:

cp .env.example .env
# then edit .env and set VIDEO_S3_SECRET (and any other values)
Variable Example Notes
VIDEO_S3_ENDPOINT http://<minio-host>:80 The S3 API endpoint (required). Use the S3 API port (e.g. 80), not the Console port.
VIDEO_S3_KEY (required) Access key.
VIDEO_S3_SECRET (required) Secret key.
VIDEO_S3_BUCKET (required) Bucket name.
VIDEO_S3_REGION us-east-1 Region (optional; any value works for MinIO).
VIDEO_S3_PROCESSED_PREFIX processed/ Existing clips browsed by the app (optional).
VIDEO_S3_UPLOAD_PREFIX app-uploads/ Where new uploads are stored (optional).

The four connection settings (ENDPOINT, KEY, SECRET, BUCKET) have no defaults in the code — the app refuses to start if any are missing. .env holds them and is gitignored — never commit it. .env.example (with values redacted) is the committed template. Real OS environment variables, if set, take precedence over .env, so you can still override per-run:

VIDEO_S3_ENDPOINT="http://localhost:9000" VIDEO_S3_BUCKET="test" python app.py

MinIO Console vs S3 API. MinIO serves a browser Console (a :9001-style port) that is separate from its S3 API port. The app uses the S3 API only — point VIDEO_S3_ENDPOINT at the API port, not the Console.

How uploads work

  • A clip's detail page lists every file in that clip's folder (the video, tracklets 0.zip, metadata.json, annotation JSON, ...) with its size and a download link. Video files are click-to-play — clicking one loads it into an inline player with seeking; nothing is streamed until you click.
  • Uploading a video creates a new prefix under app-uploads/, named after the (sanitized) video filename, and streams the file into the bucket. If that name already exists, a numeric suffix is appended (_2, _3, ...).
  • Uploading a JSON annotation auto-matches to a clip by filename: a file named clip_01.json is matched to the clip whose video is clip_01.mp4. If no matching clip is found, the upload is rejected with an error (and the closest candidates are suggested) rather than silently stored in the wrong place.
  • From a clip's detail page, you can also upload/replace its JSON directly — that upload always targets that specific clip, bypassing filename matching.
  • Building an annotation in-app. Instead of uploading a file, the clip detail page has a 🛠️ Build annotation in-app button that opens an interactive builder (persons → station HEAD/MIDDLE/TAIL → start-time rows). Clicking Save to this clip writes the generated JSON straight to that clip — no file handling. The builder auto-saves your in-progress work to the browser's localStorage (scoped per clip), so closing/reopening restores your draft; the draft is cleared once you save successfully. It's also available as a standalone page at /builder (generate + download only, no clip target).

Annotation JSON format

{
  "persons": [
    {
      "uuid": "PERSON",
      "station": "HEAD",
      "start_times": [{"id": "50002", "t": "00:00:00"}]
    }
  ]
}

station is one of HEAD / MIDDLE / TAIL. Stats shown per video: total persons, total detections, detections per station (with any station at zero detections highlighted), and the fraction of the video's duration spanned by the earliest-to-latest annotated timestamp ("time coverage").

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages