Fantàstic que ja soni l’Ona! 🎉 Et preparo l’entrada de blog completa, ben estructurada i llesta per copiar.
🎙️ TTS local amb veu catalana (Ona) a Deepin — Guia completa
1. Objectiu
Disposar d’un servidor de síntesi de veu (TTS) local al sistema operatiu Deepin que sigui:
- 100% offline (sense núvol, sense dependre de serveis externs)
- Amb veu catalana de qualitat: la veu «Ona», desenvolupada per la UPC (Universitat Politècnica de Catalunya)
- Fàcil de provar, integrar i reinstal·lar
- Basat en Piper, un motor TTS lleuger i modern basat en xarxes neuronals (VITS)
2. Estructura del sistema
┌─────────────────────────────────────────────────────┐ │ DEEPIN (host) │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ Contenidor Docker "piper" │ │ │ │ │ │ │ │ linuxserver/piper (v2.5.2-ls130) │ │ │ │ ├── Servidor Wyoming (port 10200) │ │ │ │ └── Model de veu: │ │ │ │ ca_ES-upc_ona-medium.onnx │ │ │ │ (descarregat de HuggingFace) │ │ │ └─────────────────────────────────────────────┘ │ │ │ 127.0.0.1:10200 │ │ ▼ │ │ Clients: curl / Python / Speech Dispatcher │ └─────────────────────────────────────────────────────┘
Components clau:
| Component | Descripció |
|---|---|
| Piper | Motor TTS neuronal (VITS), lleuger i ràpid, compatible amb Raspberry Pi i servidors |
| linuxserver/piper | Imatge Docker que encapsula Piper amb un servidor Wyoming (protocol estàndard de veu, compatible amb Home Assistant) |
| ca_ES-upc_ona-medium | Veu catalana «Ona» (UPC), qualitat medium, model ONNX (~60 MB) |
| Port 10200 | Port d’escolta del servidor Wyoming |
3. Flux de funcionament
Text ("Hola, sóc l'Ona")
│
▼
[Client] ── connexió TCP 127.0.0.1:10200 ──► [Servidor Wyoming/Piper]
│ │
│ 1. Envia esdeveniment "synthesize" │ Carrega model ONNX
│ {"text": "...", "voice": "..."} │ i sintetitza amb la xarxa neuronal
│ ▼
│ ◄── 2. Rep esdeveniments "audio-chunk" ─┘
│ (àudio PCM 16-bit, 22050 Hz, mono)
▼
[Àudio WAV / aplay / altaveus]
El protocol Wyoming funciona per esdeveniments JSON amb el format:
{"type": "...", "version": "...", "data_length": N}
<2 salts de línia>
<càrrega de dades (N bytes)>
4. Instal·lació
4.1 Crear el contenidor
docker run -d \ --name piper \ -p 10200:10200 \ -e PIPER_VOICE=ca_ES-upc_ona-medium \ --restart unless-stopped \ linuxserver/piper
-p 10200:10200→ exposa el port del servidor Wyoming-e PIPER_VOICE=...→ defineix la veu per defecte (la descarrega automàticament a la primera arrencada)--restart unless-stopped→ s’aixeca sol en reiniciar el sistema
4.2 Verificació
# Contenidor en marxa?
docker ps | grep piper
# El servidor respon al protocol Wyoming?
printf '{"type":"describe"}\n\n' | timeout 3 nc 127.0.0.1 10200
# Veure els logs (ha de dir "Ready")
docker logs piper
5. Prova de veu (la que ha funcionat!)
docker exec piper bash -c "echo \"Hola, soc l'Ona, la meva veu s'ha desenvolupat a la UPC de Barcelona.\" | piper --model /config/ca_ES-upc_ona-medium.onnx --output_file -" | aplay -f S16_LE -r 22050 -c 1
Com funciona aquesta comanda:
docker exec piper ...→ executa Piper dins del contenidorecho "text" | piper --model ... --output_file -→ sintetitza el text a àudio i l’envia per stdout (-)aplay -f S16_LE -r 22050 -c 1→ reprodueix l’àudio PCM (16-bit, 22050 Hz, mono) pels altaveus
6. Prova amb Python (protocol Wyoming)
Script per generar un fitxer WAV connectant-se directament al servidor del port 10200:
import socket, json, wave
s = socket.create_connection(("127.0.0.1", 10200), timeout=60)
f = s.makefile("rb")
text = "Hola, soc l'Ona. Aixo es una prova del servidor TTS."
evt = json.dumps({"type": "synthesize", "data": {"text": text, "voice": "ca_ES-upc_ona-medium"}})
f.write((evt + "\n\n").encode())
f.flush()
chunks, rate, width, channels = [], 22050, 2, 1
while True:
header = f.readline()
if not header:
break
h = json.loads(header)
t = h.get("type")
f.read(1) # segon \n del separador
data = json.loads(f.read(h.get("data_length", 0))) if h.get("data_length") else {}
if t == "audio-start":
rate = data.get("rate", rate)
width = data.get("width", width)
channels = data.get("channels", channels)
elif t == "audio-chunk":
chunks.append(f.read(h.get("payload_length", 0)))
elif t == "audio-stop":
break
s.close()
audio = b"".join(chunks)
w = wave.open("prova_ona.wav", "wb")
w.setnchannels(channels)
w.setsampwidth(width)
w.setframerate(rate)
w.writeframes(audio)
w.close()
print(f"OK: prova_ona.wav ({len(audio)} bytes)")
7. Altres operacions útils
Aturar / engegar
docker stop piper docker start piper
Desinstal·lar i reinstal·lar
docker rm -f piper docker image rm linuxserver/piper # Reinstal·lar docker run -d --name piper -p 10200:10200 \ -e PIPER_VOICE=ca_ES-upc_ona-medium \ --restart unless-stopped linuxserver/piper
8. Conclusions
- ✅ TTS local i offline amb veu catalana de qualitat (Ona, UPC)
- ✅ Instal·lació senzilla amb Docker i reinici automàtic
- ✅ Protocol Wyoming estàndard → integrable amb Home Assistant i altres apps
- ✅ També es pot usar amb Speech Dispatcher perquè les aplicacions d’escriptori parlin en català
Aquesta és la base per a l’entrada! Si vols, també puc afegir una secció amb captures de pantalla o una part sobre com integrar-ho amb Speech Dispatcher (perquè el sistema llegeixi text en català). Ho deixem així o hi afegim alguna cosa més? 😊
Aclariments
Bona pregunta! 👌 El port 10200 no l’hem triat nosaltres — és el port per defecte d’aquesta imatge i de l’ecosistema Wyoming:
Motius
- Port per defecte de
linuxserver/piper— el servidor que hi ha dins del contenidor escolta al port 10200 de manera nativa. Per això al Docker fem-p 10200:10200(port del host → port del contenidor). - Convenció de l’ecosistema Wyoming / Home Assistant — el protocol Wyoming (el que parla aquest servidor) fa servir habitualment ports del rang 10200-10300. De fet, el add-on de Piper per a Home Assistant també usa el 10200 per defecte. Així, si algun dia el connectes a Home Assistant o a una altra eina de veu, ja parlarà el mateix idioma al mateix port.
- Port sense privilegis — està per sobre del 1024, així que no cal ser root ni configuracions especials per obrir-lo.
Es pot canviar?
Sí! La part de l’esquerra del mapeig és el port del host, així que podries posar-hi el que vulguis:
docker run -d --name piper -p 10500:10200 ...
(aleshores el servidor seria accessible a 127.0.0.1:10500). Però com que 10200 és l’estàndard, és millor deixar-lo com està perquè tot l’ecosistema el reconegui sense tocar res. 😊
Molt bona pregunta! No és que canviéssim de port — són dos servidors TTS diferents que conviuen, cadascun amb el seu port i la seva funció:
Els dos servidors
| Port 8765 | Port 10200 | |
|---|---|---|
| Servei | speech-to-speech | Piper (Docker) |
| Què és | Servidor compatible amb OpenAI Realtime (TTS + STT + LLM) | Motor TTS lleuger amb veu catalana |
| Veu catalana? | No específica (Qwen3-TTS, general) | Sí: Ona (ca_ES-upc_ona-medium, UPC) |
| Instal·lació | Python venv a ~/tts-server | Docker |
| Estat actual | Aturat | En marxa ✅ |
Per què vam afegir el Piper (10200)?
Perquè volies una veu catalana de qualitat (l’Ona de la UPC). El Piper és:
- Lleuger i offline — perfecte per a un punt de treball local
- Té el model ca_ES-upc_ona-medium específic per a català
- S’integra amb Speech Dispatcher perquè el sistema llegeixi text en català
- Docker el fa fàcil d’instal·lar, actualitzar i reinstal·lar
I el 8765?
No l’hem tret! El speech-to-speech continua instal·lat a ~/tts-server, només està aturat (ho vam veure quan ss no mostrava res al port 8765). És un servidor més «complet» (xat de veu amb micròfon + LLM), però no té la veu catalana específica.
Resum: el 8765 és per al xat de veu general, i el 10200 és el que et dona l’Ona en català. Cadascú al seu port, sense conflictes! 😊
SERVIDOR TTS SERVER /home/Miquel/tts-server
Què hi ha a ~/tts-server (sí, tot el servei speech-to-speech)
/home/Miquel/tts-server/ ├── venv/ ← entorn Python amb el paquet speech-to-speech ├── prova_piper.py ← els nostres scripts de prova ├── prova_piper_dbg.py └── ...
Aquí hi ha tot el servei speech-to-speech (el del port 8765): l’entorn virtual, el paquet instal·lat i els scripts.
On és la veu de l’Ona? (⚠️ NO és aquí)
La veu Ona NO està a ~/tts-server — està dins del contenidor Docker piper, a la carpeta /config/:
# Per veure-ho: docker exec piper ls -lh /config/
Hi trobaràs:
ca_ES-upc_ona-medium.onnx ← el model de veu ca_ES-upc_ona-medium.onnx.json ← config de la veu
⚠️ Punt important
Com que al docker run no vam muntar cap volum (-v), la veu viu dins de la capa del contenidor. Si algun dia fas docker rm -f piper, la veu es perd i es tornarà a descarregar en recrear el contenidor (caldrà internet la primera vegada).
Resum:
speech-to-speech→ tot a~/tts-server✅- Ona → dins del contenidor Docker
pipera/config/✅
Si vols preservar la veu encara que esborris el contenidor, et puc explicar com muntar un volum per a /config. T’interessa? 😊
Docker no penja de cap carpeta del teu usuari — el seu directori d’emmagatzematge és a nivell de sistema: /var/lib/docker
📁 Directori arrel de Docker /var/lib/docker

/var/lib/docker/ ├── containers/ ← dades dels contenidors ├── image/ ← imatges (linuxserver/piper, etc.) ├── overlay2/ ← capes del sistema de fitxers dels contenidors ├── volumes/ ← volums (si n'hi ha) └── ...
Per confirmar-ho a la teva màquina:
docker info | grep "Docker Root Dir" # → Docker Root Dir: /var/lib/docker
🔍 I la veu de l’Ona, on és exactament?
Com que el contenidor piper no té cap volum muntat, la carpeta /config/ (amb l’Ona) viu dins de la capa del contenidor, que es troba a:
/var/lib/docker/overlay2/... (capa del contenidor piper)
✅ Conclusió important per a tu
- Docker i la veu d’Ona NO depenen de
~/tts-server. - Si esborressis
~/tts-server, el Piper i l’Ona continuarien funcionant (només perdries el serveispeech-to-speechi els scripts de prova). - I a l’inrevés: si esborres el contenidor (
docker rm -f piper), l’Ona es perd (perquè no hi ha volum) i es torna a descarregar en recrear-lo.
Si vols que l’Ona sobreviu a l’esborrat del contenidor, la solució és muntar un volum per a /config:
docker rm -f piper docker run -d --name piper \ -p 10200:10200 \ -e PIPER_VOICE=ca_ES-upc_ona-medium \ -v /home/Miquel/piper-config:/config \ --restart unless-stopped linuxserver/piper
Així la veu quedaria desada a ~/piper-config i no es perdria mai. Vols que ho fem? 😊
La veu de l’Ona, dins de l’arbre de Docker, quedaria així (a la capa del contenidor piper dins de overlay2):
/var/lib/docker/ ├── containers/ │ └── 55f8e63ad1d5.../ ← metadades del contenidor piper │ ├── config.v2.json │ └── hostconfig.json ├── image/ │ └── overlay2/ │ ├── layerdb/ ← registre de capes │ └── repositories.json ├── overlay2/ │ ├── l/ ← enllaços curts │ └── <hash_del_contenidor>/ │ └── diff/ │ └── config/ ← el /config del contenidor piper │ ├── ca_ES-upc_ona-medium.onnx ← 🎙️ LA VEU ON A │ └── ca_ES-upc_ona-medium.onnx.json └── volumes/ ← buit (no en tenim)
⚠️ Avís: aquesta ruta només és visible amb permisos de root (per això ls normal no la veu).
Si vols comprovar-ho tu mateix al terminal:
# 1. Trobar el directori on viu la veu (necessita sudo) sudo find /var/lib/docker -name "ca_ES-upc_ona*" 2>/dev/null # 2. Resultat esperat (alguna cosa semblant): # /var/lib/docker/overlay2/<hash>/diff/config/ca_ES-upc_ona-medium.onnx # /var/lib/docker/overlay2/<hash>/diff/config/ca_ES-upc_ona-medium.onnx.json
També ho pots veure «des de dins» del contenidor (això sí que ho pots fer sense sudo):
# La veu vista des del punt de vista del contenidor docker exec piper ls -lh /config/
Exacte, la teva intuïció és correcta! ✅ Però ho podem afinar perquè sigui més lleuger i fiable. Et detallo:
Què s’ha de fer backup i com
1️⃣ Servei speech-to-speech (port 8765) → ~/tts-server
# Backup tar czf tts-server-backup.tar.gz -C /home/Miquel tts-server # Restauració (al mateix camí /home/Miquel/tts-server) tar xzf tts-server-backup.tar.gz -C /home/Miquel
⚠️ Detall important: l’entorn virtual (venv) guarda camins absoluts. Si restaures al mateix camí (/home/Miquel/tts-server), funciona tal qual. Si el camí canvia, caldria recrear el venv i reinstal·lar el paquet.
2️⃣ Piper + veu Ona (port 10200) → Docker
Aquí tens dues opcions:
Opció A — Recomanada (lleugera): fer backup de la imatge + la carpeta de config
# Backup de la imatge Docker (el programa) docker save linuxserver/piper -o piper-image.tar # La veu: si muntem el volum /config (com et vaig suggerir), es desa a ~/piper-config tar czf piper-config-backup.tar.gz ~/piper-config
Opció B — Completa (tot Docker): backup de /var/lib/docker
# Requereix sudo i és més pesat sudo tar czf docker-full-backup.tar.gz /var/lib/docker
✅ El teu plantejament, confirmat PODEM FER BACKUP DEL SERVIDORS TTS
| Servei | On es desa | Com es recupera |
|---|---|---|
| speech-to-speech (8765) | ~/tts-server | Copiar la carpeta enrere |
| Piper + Ona (10200) | /var/lib/docker (imatge + capa contenidor) | Restaurar Docker o docker save + volum /config |
Resum: sí, amb un backup de ~/tts-server + Docker ho recuperes tot. Però la manera més neta és:
tarde~/tts-server(per al 8765)docker savede la imatge + volum/config(per al 10200 i l’Ona) — així no cal tocar tot/var/lib/docker
Vols que et prepari un script de backup/restauració senzill amb tot això? 😊
Endavant! Et preparo dos scripts: un de backup i un de restauració. Els pots desar on vulguis (per exemple a ~/).
📦 Script 1 — Backup (tts_backup.sh)
Desa’l amb aquesta comanda:
# Amb l'USB "backup_tot" connectat, només cal: ~/tts_backup.sh # → USB 'backup_tot' trobat a: /media/Miquel/backup_tot # → Desant a: /media/Miquel/backup_tot/20260913_... # Si l'USB no està connectat, et dirà que el connectis
Sí! Podem fer que el script busqui automàticament l’USB amb l’etiqueta backup_tot. Desa aquesta versió:
cat > ~/tts_backup.sh << 'EOF'
#!/bin/bash
# ============================================
# Backup del sistema TTS local (Deepin)
# Us: ./tts_backup.sh [carpeta_destinacio]
# Sense arguments: busca automaticament l'USB "backup_tot"
# Exemple: ./tts_backup.sh /media/Miquel/backup_tot
# ============================================
set -e
USB_LABEL="backup_tot"
DEST=""
# 1) Si es passa una carpeta, s'usa aquesta
if [ -n "$1" ]; then
DEST="$1"
else
# 2) Si no, buscar l'USB amb l'etiqueta backup_tot
DEST=$(findmnt -rn -o TARGET -S LABEL="$USB_LABEL" 2>/dev/null | head -1)
if [ -z "$DEST" ]; then
DEST=$(ls -d /media/*/"$USB_LABEL" /run/media/*/"$USB_LABEL" 2>/dev/null | head -1)
fi
if [ -n "$DEST" ]; then
echo "USB '$USB_LABEL' trobat a: $DEST"
fi
fi
# Si no s'ha trobat cap destinacio, error
if [ -z "$DEST" ]; then
echo "ERROR: no s'ha trobat la destinacio."
echo " - Connecta l'USB '$USB_LABEL' i munta'l"
echo " - O indica una carpeta: $0 /ruta/destinacio"
exit 1
fi
# Comprovar permisos
if [ ! -d "$DEST" ] || [ ! -w "$DEST" ]; then
echo "ERROR: la destinacio '$DEST' no existeix o no es pot escriure."
exit 1
fi
DATA=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$DEST/$DATA"
mkdir -p "$BACKUP_DIR"
echo "Desant a: $BACKUP_DIR"
echo "=== 1/3 Backup de ~/tts-server (speech-to-speech) ==="
if [ -d "$HOME/tts-server" ]; then
tar czf "$BACKUP_DIR/tts-server.tar.gz" -C "$HOME" tts-server
echo "OK -> tts-server.tar.gz"
else
echo "AVIS: ~/tts-server no existeix, s'omet."
fi
echo "=== 2/3 Backup de la imatge Docker linuxserver/piper ==="
if docker image inspect linuxserver/piper >/dev/null 2>&1; then
docker save linuxserver/piper -o "$BACKUP_DIR/piper-image.tar"
echo "OK -> piper-image.tar"
else
echo "AVIS: imatge linuxserver/piper no trobada, s'omet."
fi
echo "=== 3/3 Backup de la veu Ona (/config del contenidor piper) ==="
if docker ps -a --format '{{.Names}}' | grep -qx piper; then
mkdir -p "$BACKUP_DIR/piper-config"
docker cp piper:/config/. "$BACKUP_DIR/piper-config/"
echo "OK -> piper-config/"
else
echo "AVIS: contenidor piper no existeix, s'omet."
fi
echo ""
echo "=== RESUM ==="
du -sh "$BACKUP_DIR"/*
echo "Backup completat a: $BACKUP_DIR"
EOF
chmod +x ~/tts_backup.sh
🚀 Com funciona ara
# Amb l'USB "backup_tot" connectat, només cal: ~/tts_backup.sh # → USB 'backup_tot' trobat a: /media/Miquel/backup_tot # → Desant a: /media/Miquel/backup_tot/20260913_... # Si l'USB no està connectat, et dirà que el connectis
Com ho detecta:
- Primer prova
findmnt -S LABEL=backup_tot(busca per etiqueta a tots els punts de muntatge) - Si no, busca a les rutes típiques
/media/*/backup_toti/run/media/*/backup_tot - Encara pots forçar una ruta manual si vols:
~/tts_backup.sh /altre/lloc
⚠️ Recorda: l’USB ha d’estar connectat i muntat abans d’executar el script. Vols provar-ho? 😊
♻️ Script 2 — Restauració (tts_restore.sh)
cat > ~/tts_restore.sh << 'EOF'
#!/bin/bash
# ============================================
# Restauració del sistema TTS local (Deepin)
# Us: ./tts_restore.sh <directori_backup>
# ============================================
set -e
if [ -z "$1" ]; then
echo "Us: $0 <directori_backup>"
echo "Exemple: $0 ~/tts-backups/20260913_120000"
exit 1
fi
BACKUP_DIR="$1"
echo "=== 1/3 Restauracio de ~/tts-server ==="
if [ -f "$BACKUP_DIR/tts-server.tar.gz" ]; then
tar xzf "$BACKUP_DIR/tts-server.tar.gz" -C "$HOME"
echo "OK: tts-server restaurat"
else
echo "AVIS: no hi ha tts-server.tar.gz"
fi
echo "=== 2/3 Restauracio de la imatge Docker ==="
if [ -f "$BACKUP_DIR/piper-image.tar" ]; then
docker load -i "$BACKUP_DIR/piper-image.tar"
echo "OK: imatge carregada"
else
echo "AVIS: no hi ha piper-image.tar"
fi
echo "=== 3/3 Recreacio del contenidor piper i restauracio de la veu ==="
if docker ps -a --format '{{.Names}}' | grep -qx piper; then
echo "El contenidor piper ja existeix; l'elimino per recrear-lo."
docker rm -f piper
fi
docker create --name piper \
-p 10200:10200 \
-e PIPER_VOICE=ca_ES-upc_ona-medium \
--restart unless-stopped \
linuxserver/piper
if [ -d "$BACKUP_DIR/piper-config" ]; then
docker cp "$BACKUP_DIR/piper-config/." piper:/config/
echo "OK: veu Ona restaurada a /config"
else
echo "AVIS: no hi ha piper-config; el contenidor descarregara la veu de nou."
fi
docker start piper
echo ""
echo "=== VERIFICACIO ==="
docker ps | grep piper || true
echo "Restauracio completada."
EOF
chmod +x ~/tts_restore.sh
🚀 Com s’utilitzen
# 1. Fer un backup (es desa a ~/tts-backups/AMB_DATA) ~/tts_backup.sh # 2. Veure els backups disponibles ls -lh ~/tts-backups/ # 3. Restaurar (indicant el directori del backup) ~/tts_restore.sh ~/tts-backups/20260913_120000
Avís: la restauració elimina i recrea el contenidor piper si ja existeix (la veu es recupera des del backup, sense necessitat de descarregar-la).
Vols que t’obri el terminal per crear els scripts i provar el backup? 😊