mcspeak/Dockerfile
Ryan Malloy 27b4c4ae29 Add generate_audio file/format params; bring Piper TTS up with es_MX default
generate_audio gains output_path + format params (wav, mp3, ogg, flac,
m4a). Non-wav formats route through ffmpeg (added to the Dockerfile).
Files land under /output/ inside the container, bind-mounted from the
host's TTS_OUTPUT_HOST_DIR (defaults to ~/mcspeak-out). Subdirectories
are auto-created. Path validation rejects writes outside /output/ —
both absolute escapes like /etc/passwd and traversal forms like
../../etc/passwd are canonicalized and checked against the mount root.
Extension auto-corrects to match the requested format.

piper-tts service added to compose using rhasspy/wyoming-piper. Default
voice es_MX-ald-medium is pre-warmed at container start and persists in
./piper-data/ across recreates (~60 MB download on first run). Port
10200 published to localhost so host-side scripts can also reach
Wyoming directly; mcspeak inside the stack uses container DNS. Voice
configurable via TTS_PIPER_VOICE — the same env var threads through to
both wyoming-piper's --voice flag and mcspeak's settings.piper_voice,
so they stay in sync.

PiperEngine.__init__ accepts a default_voice override; settings.piper_voice
threads it from the env. list_engines now reports the configured default
rather than the hardcoded en_US-lessac-medium. speak() / generate_audio()
calls with engine=piper and no explicit voice use the configured one.

mcspeak-internal network changed from internal:true to default-bridge.
internal:true was overkill — the goal was per-stack DNS scoping (which
compose provides automatically via the project-prefixed network name),
not internet isolation. The latter broke piper-tts's HuggingFace voice
download with "Name resolution failure" on first start.

.gitignore: add piper-data/ so the downloaded voice models stay out of
the repo.
2026-06-13 17:35:36 -06:00

46 lines
1.5 KiB
Docker

FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
# PipeWire client tools — pw-play connects to the host's PipeWire socket
# pulseaudio-utils provides pactl for media ducking (volume control via PulseAudio compat)
# ffmpeg handles WAV→{mp3,ogg,flac,m4a} conversion for generate_audio's format param
RUN apt-get update && apt-get install -y --no-install-recommends \
pipewire-bin \
pulseaudio-utils \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
# Create venv
RUN uv venv
# Install CPU-only torch first (saves ~3GB vs CUDA — SNAC runs on CPU anyway)
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
# Install project dependencies (torch already satisfied, won't redownload)
COPY pyproject.toml ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --no-deps -r pyproject.toml 2>/dev/null || true
# Copy source and install project
COPY src/ src/
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install .
# Non-root user matching host uid for PipeWire socket access
RUN useradd -u 1000 -m tts \
&& mkdir -p /home/tts/.cache/huggingface /data \
&& chown -R tts:tts /home/tts/.cache /data
USER tts
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8371
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s \
CMD python -c "import socket; s=socket.create_connection(('127.0.0.1',8371),2); s.close()" || exit 1
CMD ["mcspeak"]