TTS local amb veu catalana (Ona) a Deepin — Guia completa

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:

ComponentDescripció
PiperMotor TTS neuronal (VITS), lleuger i ràpid, compatible amb Raspberry Pi i servidors
linuxserver/piperImatge Docker que encapsula Piper amb un servidor Wyoming (protocol estàndard de veu, compatible amb Home Assistant)
ca_ES-upc_ona-mediumVeu catalana «Ona» (UPC), qualitat medium, model ONNX (~60 MB)
Port 10200Port 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:

  1. docker exec piper ... → executa Piper dins del contenidor
  2. echo "text" | piper --model ... --output_file - → sintetitza el text a àudio i l’envia per stdout (-)
  3. 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

  1. 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).
  2. 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.
  3. 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 8765Port 10200
Serveispeech-to-speechPiper (Docker)
Què ésServidor 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-serverDocker
Estat actualAturatEn 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 piper a /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 servei speech-to-speech i 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

ServeiOn es desaCom es recupera
speech-to-speech (8765)~/tts-serverCopiar 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:

  1. tar de ~/tts-server (per al 8765)
  2. docker save de 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:

  1. Primer prova findmnt -S LABEL=backup_tot (busca per etiqueta a tots els punts de muntatge)
  2. Si no, busca a les rutes típiques /media/*/backup_tot i /run/media/*/backup_tot
  3. 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? 😊