mirror of
https://github.com/hanzoai/speech.git
synced 2026-10-04 07:37:19 +00:00
A decoder costs the same for silence as for speech, so an attendant in a quiet room occupied a worker and billed a tenant for text that came back empty. Silero already ships inside faster-whisper and already runs inside every decode; running it in FRONT of the decoder — 0.67 ms per 250 ms push, measured — turns that second into no decode and no charge at all. It is a squelch rather than a per-window classifier, because the classic way to lose more than you save is to start at the trigger: the voice is not detected until it is under way. Sliding a word across a push shows the size of it — with no lead-in, 8 of 32 alignments clip up to 71.8 ms off the front of the word, and with LEAD they keep 428 ms of margin. `duration` on the ack becomes the seconds that reached a decoder, so the plane meters what we listened to. It can exceed one push's own length, on the push that hands over the lead-in, and can never exceed the audio a session was sent. Co-authored-by: Hanzo Dev <[email protected]>
40 lines
2.0 KiB
Docker
40 lines
2.0 KiB
Docker
FROM python:3.12-slim AS base
|
|
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg curl && rm -rf /var/lib/apt/lists/*
|
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
WORKDIR /app
|
|
COPY pyproject.toml uv.lock ./
|
|
RUN uv sync --frozen --no-dev
|
|
COPY main.py stt.py tts.py transcript.py vad.py ./
|
|
|
|
# The gate, as a build stage. `docker build` is the ONE thing this lane is
|
|
# already known to do, so the test needs no uv on the runner and no bind mount
|
|
# — the two ways a step here can fail for reasons that have nothing to do with
|
|
# the code. It sits ABOVE the weight bake on purpose: the suite stubs both model
|
|
# calls, so it needs no weights, and running it first means a contract break is
|
|
# caught before 350MB is downloaded rather than after.
|
|
FROM base AS test
|
|
COPY test_speech.py ./
|
|
RUN uv sync --frozen --group dev && uv run pytest -q
|
|
|
|
# Weights bake at BUILD: a deterministic image that boots without the network.
|
|
FROM base AS weights
|
|
RUN uv run python -c "import stt; [stt.load(m) for m in stt.MODELS]" \
|
|
&& curl -fsSLo kokoro-v1.0.onnx https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx \
|
|
&& curl -fsSLo voices-v1.0.bin https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin
|
|
|
|
# The second gate: the same code against the REAL models. The suite above replaces
|
|
# faster-whisper with a stand-in so the window arithmetic can be checked exactly,
|
|
# and a suite that only ever sees a stand-in cannot tell a working decoder from a
|
|
# deleted one. Here kokoro speaks a sentence and whisper has to say it back, so
|
|
# the weights, the raw-audio path and the sample scaling are all load-bearing.
|
|
#
|
|
# It sits above `final` and below nothing: `final` does not depend on it, so an
|
|
# untargeted `docker build` still produces the image WITHOUT dev dependencies.
|
|
FROM weights AS live
|
|
COPY test_speech.py test_live.py ./
|
|
RUN uv sync --frozen --group dev && uv run pytest -q
|
|
|
|
FROM weights AS final
|
|
EXPOSE 8000
|
|
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|