Skip to content

mbari-org/beholder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

97 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

beholder

Build

MBARI logo

beholder

Overview

Beholder is a Scala 3 HTTP microservice that extracts individual frames from videos as JPEG or PNG images using ffmpeg. It exposes POST /capture endpoints that accept a video URL and an elapsed time in milliseconds, return the image, and cache results on disk to avoid redundant ffmpeg invocations.

Requirements

  • Java 21+
  • ffmpeg installed and on PATH
  • sbt (Scala Build Tool)

API

POST /capture

Capture a frame from a video at the given elapsed time. Returns the image directly (JPEG by default), or a cached copy if one exists. Use the optional imageType field to request a PNG instead.

Headers

Header Required Description
X-Api-Key Yes API key for access (default: foo)
Content-Type Yes Must be application/json

Query Parameters

Parameter Default Description
accurate true When true, seek to the exact elapsed time. When false, seek to the nearest keyframe before it (faster)
nokey false When true, skip non-key frames after the seek point. Useful for fast processing of some codecs

Request Body

Field Required Description
videoUrl Yes URL of the video file
elapsedTimeMillis Yes Elapsed time in milliseconds
imageType No "jpg" or "png" (default: "jpg")
{
  "videoUrl": "http://m3.shore.mbari.org/videos/M3/proxy/DocRicketts/2022/03/1436/D1436_20220322T132758Z_h264.mp4",
  "elapsedTimeMillis": 1234,
  "imageType": "png"
}

Important

PNGs are considerably larger than JPEGs and will increase latency as well as reduce the number of images that can be stored in cache.

Example — JPEG (default)

curl -X POST http://localhost:8080/capture \
  -H "X-Api-Key: foo" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"http://example.com/video.mp4","elapsedTimeMillis":1234}' \
  --output frame.jpg

Example — PNG

curl -X POST http://localhost:8080/capture \
  -H "X-Api-Key: foo" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"http://example.com/video.mp4","elapsedTimeMillis":1234,"imageType":"png"}' \
  --output frame.png

Example with query params

curl -X POST "http://localhost:8080/capture?accurate=false&nokey=true" \
  -H "X-Api-Key: foo" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"http://example.com/video.mp4","elapsedTimeMillis":5000}' \
  --output frame.jpg

POST /capture/jpg

Convenience endpoint that always returns a JPEG, ignoring any imageType in the request body. Accepts the same headers, query parameters, and body fields as POST /capture.

curl -X POST http://localhost:8080/capture/jpg \
  -H "X-Api-Key: foo" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"http://example.com/video.mp4","elapsedTimeMillis":1234}' \
  --output frame.jpg

POST /capture/png

Convenience endpoint that always returns a PNG, ignoring any imageType in the request body. Accepts the same headers, query parameters, and body fields as POST /capture.

curl -X POST http://localhost:8080/capture/png \
  -H "X-Api-Key: foo" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"http://example.com/video.mp4","elapsedTimeMillis":1234}' \
  --output frame.png

GET /health

Returns HTTP 200 if the service is running.

Swagger UI

Interactive API documentation is available at http://localhost:8080/docs when the server is running.

Configuration

Settings can be provided via environment variables, a config file, or CLI flags. CLI flags take the highest precedence, followed by environment variables, then the defaults in reference.conf.

Config key Environment variable CLI flag Default
beholder.api.key BEHOLDER_API_KEY -k / --apikey foo
beholder.http.port BEHOLDER_HTTP_PORT -p / --port 8080
beholder.cache.size BEHOLDER_CACHE_SIZE -c / --cachesize 500 (MB)
beholder.cache.freepct BEHOLDER_CACHE_FREEPCT -f / --freepct 0.20

The cache root directory is a required positional CLI argument (or BEHOLDER_CACHE_ROOT env var when running via Docker).

Development

Building

# Compile
sbt compile

# Run all tests (requires ffmpeg)
sbt test

# Run a single test suite
sbt "testOnly org.mbari.beholder.JpegCacheSuite"

# Format code (run before committing)
sbt scalafmtAll

# Generate scaladoc
sbt doc

# Check for dependency updates
sbt dependencyUpdates

Running Locally

# Start the server with a cache directory
sbt "run /tmp/beholder-cache"

# With custom port and API key
sbt "run --port 9090 --apikey secret /tmp/beholder-cache"

Cache layout

Frames are stored under the cache root as:

<root>/<video-url-host>/<video-url-path>/<hh_mm_ss.sss>.<ext>

where <ext> is jpg or png depending on the requested image type. For example:

/tmp/beholder-cache/m3.shore.mbari.org/videos/M3/proxy/DocRicketts/2022/03/1436/D1436_20220322T132758Z_h264.mp4/00_00_01.234.jpg
/tmp/beholder-cache/m3.shore.mbari.org/videos/M3/proxy/DocRicketts/2022/03/1436/D1436_20220322T132758Z_h264.mp4/00_00_01.234.png

JPEG and PNG versions of the same frame are cached independently. The in-memory index is rebuilt from disk on startup, so cached frames survive service restarts.

Deployment

Docker

Pre-built multi-architecture images (linux/amd64, linux/arm64) are published to Docker Hub as mbari/beholder.

docker run -d \
  -p 8080:8080 \
  -e BEHOLDER_API_KEY=your-secret-key \
  -v /data/beholder-cache:/opt/beholder/cache \
  mbari/beholder:latest

The container expects a writable volume mounted at /opt/beholder/cache. The port defaults to 8080.

Environment variables for Docker

docker run -d \
  -p 9090:9090 \
  -e BEHOLDER_API_KEY=your-secret-key \
  -e BEHOLDER_HTTP_PORT=9090 \
  -e BEHOLDER_CACHE_SIZE=1000 \
  -e BEHOLDER_CACHE_FREEPCT=0.25 \
  -v /data/beholder-cache:/opt/beholder/cache \
  mbari/beholder:latest

Building a Docker Image

Standard build (from any platform targeting linux/amd64)

sbt 'Docker / publish'

Multi-architecture build (required on Apple Silicon)

./build.sh

This uses docker buildx to produce linux/amd64 and linux/arm64 images and pushes them to Docker Hub. Run docker login first.

Releasing

Releases are triggered automatically by pushing a git tag that matches the version pattern [0-9]+.* (no leading v):

git tag 1.2.3
git push origin 1.2.3

The release workflow builds and pushes a multi-arch Docker image tagged with both the version number and latest.

Documentation

Full scaladoc is published at https://mbari-org.github.io/beholder/

Additional documentation can be added as Markdown files in src/docs/ and will be included automatically when running sbt doc.


Beholder image Copyright © 2022 Aine Schlining

About

Service that extracts images from video (with caching)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors