speech-server

speech-server; her Soniqo modelini basit bir REST API ile sunan ve /v1/realtime adresinde OpenAI Realtime API uyumlu bir WebSocket sağlayan yerel bir HTTP + WebSocket sunucusudur. speech CLI ile aynı Homebrew bottle içinde gelir — brew install speech her ikisini birden PATH'inize yerleştirir.

Kurulum ve çalıştırma

brew install soniqo/tap/speech

speech-server --port 8080
# Starting server on http://127.0.0.1:8080
# Endpoints:
#   POST /transcribe  - Speech-to-text (WAV body or JSON with audio_base64)
#   POST /speak       - Text-to-speech (JSON: {text, engine?, language?})
#   POST /respond     - Speech-to-speech (WAV body, voice/max_steps via query)
#   POST /enhance     - Speech enhancement (WAV body)
#   GET  /health      - Health check
#   WS   /v1/realtime - OpenAI Realtime API (JSON events, base64 PCM16 audio)

Komut satırı bayrakları

BayrakVarsayılanAçıklama
--host127.0.0.1Bind adresi. LAN'a açmak için 0.0.0.0 olarak değiştirin.
--port8080TCP portu.
--preloadkapalıTüm modelleri başlangıçta hevesle yükler. Boot daha yavaş (~30–60 sn) ama ilk istek gecikmesi sıfırdır.

Modeller ilk kullanımda indirilir ve ~/Library/Caches/qwen3-speech/ içinde önbelleğe alınır. Belirli bir model için ilk istek, indirme + yükleme maliyetini öder (model boyutuna göre 30 sn – 2 dk); sonraki istekler sıcaktır.

REST uç noktaları

POST /transcribe — Konuşmadan metne

Ya ham bir WAV gövdesi ya da base64 ile kodlanmış ses içeren bir JSON zarfı kabul eder.

# WAV body (preferred — lower overhead)
curl -X POST http://127.0.0.1:8080/transcribe \
  -H "Content-Type: audio/wav" \
  --data-binary @recording.wav

# JSON with base64
curl -X POST http://127.0.0.1:8080/transcribe \
  -H "Content-Type: application/json" \
  -d '{"audio_base64":"'"$(base64 -i recording.wav)"'","language":"en"}'

Yanıt: {"text": "…", "language": "en", "confidence": 0.93}.

POST /speak — Metinden konuşmaya

curl -X POST http://127.0.0.1:8080/speak \
  -H "Content-Type: application/json" \
  -d '{"text":"Hello, world!","engine":"kokoro","language":"en"}' \
  --output hello.wav

Yanıt gövdesi bir WAV blob'udur. Desteklenen engine değerleri: qwen3 (varsayılan), cosyvoice, kokoro.

POST /respond — Konuşmadan konuşmaya

curl -X POST "http://127.0.0.1:8080/respond?voice=en_female_calm&max_steps=256" \
  -H "Content-Type: audio/wav" \
  --data-binary @question.wav \
  --output answer.wav

PersonaPlex 7B'yi çalıştırır — ses girer, ses çıkar. Transkript bir X-Response-Text başlığında döndürülür. Ses preset adları için PersonaPlex kılavuzuna bakın.

POST /enhance — Konuşma iyileştirme

curl -X POST http://127.0.0.1:8080/enhance \
  -H "Content-Type: audio/wav" \
  --data-binary @noisy.wav \
  --output clean.wav

48 kHz'de DeepFilterNet3. Giriş gerekirse yeniden örneklenir.

GET /health — Canlılık denetimi

curl http://127.0.0.1:8080/health
# {"status":"ok"}

WebSocket: /v1/realtime

OpenAI Realtime API ile doğrudan uyumlu — aynı JSON olay şeması (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) ile 24 kHz'de base64 kodlu PCM16 ses. OpenAI'ın Realtime SDK'sına karşı yazılmış istemciler, kod değişikliği olmadan (yalnızca WebSocket URL'sini değiştirerek) speech-server ile çalışır.

JavaScript örneği

const ws = new WebSocket("ws://127.0.0.1:8080/v1/realtime");

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "session.update",
    session: { modalities: ["audio", "text"] }
  }));

  // Stream PCM16 mono 24kHz audio from the mic:
  const audioBase64 = await capturePCM16Chunk();
  ws.send(JSON.stringify({
    type: "input_audio_buffer.append",
    audio: audioBase64
  }));

  ws.send(JSON.stringify({ type: "response.create" }));
};

ws.onmessage = ev => {
  const msg = JSON.parse(ev.data);
  if (msg.type === "response.audio.delta") {
    playPCM16Base64(msg.delta);
  }
};

Python örneği

import asyncio, base64, json, wave, websockets

async def main():
    async with websockets.connect("ws://127.0.0.1:8080/v1/realtime") as ws:
        await ws.send(json.dumps({"type": "session.update",
                                  "session": {"modalities": ["audio", "text"]}}))

        with wave.open("question.wav", "rb") as wav:
            pcm16 = wav.readframes(wav.getnframes())
        await ws.send(json.dumps({"type": "input_audio_buffer.append",
                                  "audio": base64.b64encode(pcm16).decode()}))
        await ws.send(json.dumps({"type": "response.create"}))

        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "response.audio.delta":
                open("answer.pcm", "ab").write(base64.b64decode(msg["delta"]))
            elif msg["type"] == "response.done":
                break

asyncio.run(main())

Dağıtım notları

Kaynak kodu