kokoro: persist custom voices across container recreate

Wrapper only enumerates one voice directory (settings.voices_dir,
default /app/api/src/voices/v1_0 — inside the container's writable
layer, not bind-mounted). Override via VOICES_DIR=/app/user_voices
(host bind mount) and add a command shim that cp -r's built-ins from
the in-image v1_0 into user_voices on every start. Built-ins re-seed
fresh from the image (so upgrades that add voices propagate); custom
.pt files in user_voices are preserved (cp -r is additive).

Also adds scripts/blend_kokoro_voice.py + a playbook around it that
mirrors the wrapper's request-time voice="a(w)+b(w)" math but writes
the result as a named .pt to user_voices, making it discoverable via
GET /v1/audio/voices and persistent across recreate. Defaults to
athena = af_bella(2)+af_aoede(1) normalized.
This commit is contained in:
vh
2026-05-10 17:48:24 -07:00
parent a61b577c59
commit c5ab99e74f
5 changed files with 231 additions and 11 deletions
+11
View File
@@ -32,6 +32,17 @@ steps:
shell: mkdir -p {{ user_voices_dir }}
creates: "{{ user_voices_dir }}"
- name: Chown user-voices to 1001:1001 (appuser inside container)
# The compose `command:` shim runs as appuser (image USER) and
# cp's the in-image built-in voicepacks into this dir on every
# container start. appuser is uid 1001 inside the container; the
# host has no user with that uid, so we chown numerically. Once
# set, the shim can write; the blend script (run via docker exec
# -u 0) writes as root, producing 0644 files appuser can read.
shell: chown 1001:1001 {{ user_voices_dir }}
sudo: true
when: '[ "$(stat -c %u:%g {{ user_voices_dir }})" != "1001:1001" ]'
- name: Ensure voices dir exists (used only if compose mount is enabled)
shell: mkdir -p {{ voices_dir }}
creates: "{{ voices_dir }}"