- Python 97.7%
- Dockerfile 2.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Publish container image / publish (push) Successful in 2m8s
|
||
| .forgejo/workflows | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| app.py | ||
| Dockerfile | ||
| inference.py | ||
| LICENSE.silero-vad | ||
| pyproject.toml | ||
| README.md | ||
| segmentation.py | ||
| uv.lock | ||
Home VAD
Unauthenticated, CPU-only HTTP API for Silero VAD. Accepts an audio file and returns speech intervals, not transcription, speaker identification, or wake-word detection. No streaming API or infrastructure manifests are included.
Model provisioning
The image contains the API and dependencies, not Silero model weights. Neither
the Docker build nor runtime downloads models. Provision the model separately and
mount it read-only, readable by UID/GID 10001.
MODEL_PATH is required: an absolute path to the mounted file, not a directory
or remote URL. The supported artifact is the standard recurrent ONNX model:
| Property | Value |
|---|---|
| Upstream version | v6.2.2 |
| Commit | 60b7ffa243625ebdc1070275a29f18c87843786a |
| Upstream path | src/silero_vad/data/silero_vad.onnx |
| SHA-256 | 1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3 |
Download on the provisioning machine, outside the image build:
mkdir -p models
curl --fail --location \
https://raw.githubusercontent.com/snakers4/silero-vad/60b7ffa243625ebdc1070275a29f18c87843786a/src/silero_vad/data/silero_vad.onnx \
--output models/silero_vad.onnx
printf '%s %s\n' \
1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3 \
models/silero_vad.onnx | sha256sum --check
chmod 644 models/silero_vad.onnx
Checksum verification belongs to provisioning; runtime checks the model interface and executes a warmup frame before becoming ready. Missing, unreadable, corrupt, or incompatible models fail startup. JIT, sequence, and alternate ONNX variants are not supported. No automatic fallback is used.
Build and run
docker build -t home-vad:local .
docker run --rm --name home-vad \
--publish 127.0.0.1:8000:8000 \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=256m \
--env MODEL_PATH=/models/silero_vad.onnx \
--mount type=bind,src="$(pwd)/models/silero_vad.onnx",dst=/models/silero_vad.onnx,readonly \
home-vad:local
The image uses Python 3.12, locked uv dependencies, CPU ONNX Runtime, and one
Uvicorn worker on port 8000. Inference uses one thread and processes one file at
a time. No PyTorch, TorchAudio, Silero Python package, or GPU runtime is installed.
The Silero package is deliberately avoided because it includes model assets and
Torch dependencies.
API
curl --fail-with-body http://localhost:8000/vad \
-F 'file=@mensagem.ogg'
Send exactly one multipart file named file, with no additional fields. PyAV
decodes common formats such as WAV, Ogg/Opus, MP3, and FLAC and resamples/downmixes
the first audio stream to mono 16 kHz. Files referring to external resources are
not supported; decoding disables external file/network protocols.
Example response:
{
"duration_seconds": 5.12,
"segments": [{"start": 0.48, "end": 2.91}]
}
Timestamps are seconds on the decoded recording's timeline, before any speech
removal. Endpoints are bounded by the decoded duration; the end is exclusive.
No speech is a successful response with "segments": []. Results do not include
frame probabilities or a segment-level confidence score.
Segmentation follows upstream defaults: entry threshold 0.5, exit threshold
0.35, minimum speech 250 ms (strictly greater, as upstream implements it),
minimum silence 100 ms, and padding 30 ms. There is no maximum segment duration
other than the file duration limit. Model frames are 512 samples (32 ms); the last
frame is zero-padded, but output timestamps never extend into that padding.
Recurrent state and context are new for every request, including after failures.
| Endpoint | Purpose |
|---|---|
POST /vad |
Detect speech in an uploaded audio file |
GET /healthz |
Process liveness; returns ok |
GET /readyz |
Model loaded and service accepting work; returns ok or HTTP 503 |
GET /docs |
Interactive API documentation |
Limits and errors
- Maximum file: 25 MiB; total multipart body: 25 MiB + 64 KiB.
- Maximum decoded audio: 600 seconds, checked before each frame is resampled and again against the resampled output size.
- Upload timeout: 30 seconds.
- At most nine outstanding requests, including uploads, active work, and
queued work. One detection runs at a time; excess requests receive HTTP 503
with
Retry-After: 5. Queue saturation does not fail readiness. - Disconnecting/cancelling skips queued work. Active native inference retains its slot until it finishes. Normal shutdown stops admission and drains admitted jobs; the container's Uvicorn graceful HTTP timeout is 30 seconds.
- HTTP 400: malformed multipart/headers; 408: upload timeout; 413: size/duration limit; 415: incorrect content type; 422: empty/invalid audio or missing file; 500: internal inference failure; 503: unavailable capacity. Internal exception details are logged server-side, not returned to clients.
Uploads may spool temporarily to /tmp; they are closed after parsing. Audio is
not stored persistently or logged. /docs may fetch UI assets from a CDN in the
browser; inference and the API do not require internet access.
Forgejo publishing
.forgejo/workflows/docker-image.yml follows home-whisper/home-classifier:
- Pushes to
masterand manual dispatch onmasterbuild and publish the image. - Image:
git.brennoflavio.com.br/brennoflavio/home-vad:YYYYMMDDHHMMSS(UTC). - Registry cache tag:
build-cache; nolatesttag, Git tags, or releases. - Required secrets:
REGISTRY_USERNAMEandREGISTRY_PASSWORD, with image push access. Actions are loaded fromgit.brennoflavio.com.br/ci-actions. - The runner needs Docker/Buildx and access to the registry, base images, and dependency indexes. It does not need model weights.
- Like the sibling workflows, this is a publishing job, not a separate test job. Run validation before pushing. It does not deploy infrastructure.
The workflow builds the runner's native architecture (validated locally on Linux amd64); it does not promise a multi-architecture image.
Validation
uv sync --frozen
uv run pytest
uv run ruff check .
uv run ruff format --check .
Default tests use fake inference sessions and real audio decoding; they never download models. To include a real-model silence/state-isolation test:
VAD_TEST_MODEL_PATH="$(pwd)/models/silero_vad.onnx" uv run pytest
An optional timestamp parity test compares 1,000 randomized cases with the pinned upstream implementation, without importing Torch. Download the reference source separately (the test verifies its SHA-256 before executing its pure timestamp function):
curl --fail --location \
https://raw.githubusercontent.com/snakers4/silero-vad/60b7ffa243625ebdc1070275a29f18c87843786a/src/silero_vad/utils_vad.py \
--output /tmp/silero-v6.2.2-utils.py
VAD_TEST_UPSTREAM_PATH=/tmp/silero-v6.2.2-utils.py uv run pytest tests/test_upstream.py
Before deployment, smoke-test a real speech recording and silence, measure CPU,
peak memory, and latency on the target node, and keep the unauthenticated service
internal. Future manifests should mount weights read-only, allow writable /tmp,
set resource limits, and configure startup/readiness on /readyz and liveness on
/healthz. Network policy should limit callers and deny unnecessary egress.
Attribution
segmentation.py and the ONNX frame handling in inference.py are adapted from
Silero VAD v6.2.2 at the commit above. The timestamp adaptation removes options not
used by this service and returns sample coordinates before conversion to seconds.
Upstream's MIT notice is in LICENSE.silero-vad and is included in the image.