speech-server
speech-server هو خادم محلّي يعمل على HTTP وWebSocket يُتيح كلّ نموذج من نماذج Soniqo عبر واجهة REST بسيطة، إلى جانب WebSocket متوافق مع OpenAI Realtime API على /v1/realtime. يُشحن في الحزمة نفسها من Homebrew مع واجهة speech السطرية — يضع brew install speech كليهما في PATH الخاصّ بك.
التثبيت والتشغيل
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)
وسوم سطر الأوامر
| الوسم | الافتراضي | الوصف |
|---|---|---|
--host | 127.0.0.1 | عنوان الربط. غيّره إلى 0.0.0.0 للإتاحة على الشبكة المحلّية. |
--port | 8080 | منفذ TCP. |
--preload | معطَّل | تحميل جميع النماذج بنشاط عند بدء التشغيل. إقلاع أبطأ (نحو 30–60 ثانية) لكن دون أيّ زمن استجابة لأوّل طلب. |
تُنزَّل النماذج عند أوّل استخدام وتُخزَّن في ~/Library/Caches/qwen3-speech/. يدفع الطلب الأوّل لأيّ نموذج تكلفة التنزيل والتحميل (من 30 ثانية إلى دقيقتين بحسب حجم النموذج)؛ أمّا الطلبات اللاحقة فتكون ساخنة.
نقاط نهاية REST
POST /transcribe — كلام إلى نص
يقبل إمّا متن WAV خامًا أو غلاف JSON يحتوي على صوت مُشفَّر بصيغة base64.
# 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"}'
الاستجابة: {"text": "…", "language": "en", "confidence": 0.93}.
POST /speak — نص إلى كلام
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
متن الاستجابة هو ملفّ WAV. القيم المدعومة لـ engine: qwen3 (الافتراضي)، cosyvoice، kokoro.
POST /respond — كلام إلى كلام
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 — كلام دخل، كلام خرج. يُعاد النصّ المفرَّغ في ترويسة X-Response-Text. راجع دليل PersonaPlex للاطّلاع على أسماء الإعدادات الصوتية المسبقة.
POST /enhance — تحسين الكلام
curl -X POST http://127.0.0.1:8080/enhance \
-H "Content-Type: audio/wav" \
--data-binary @noisy.wav \
--output clean.wav
DeepFilterNet3 عند 48 kHz. تُعاد عيّنة المدخلات إذا لزم الأمر.
GET /health — فحص الجاهزية
curl http://127.0.0.1:8080/health
# {"status":"ok"}
WebSocket: /v1/realtime
متوافق فورًا مع OpenAI Realtime API — مخطّط أحداث JSON نفسه (session.update، input_audio_buffer.append، response.create، response.audio.delta، …) مع صوت PCM16 مُشفَّر بصيغة base64 عند 24 kHz. تعمل العملاء المكتوبون لـ SDK الزمن الفعلي من OpenAI مقابل speech-server دون أيّ تغيير في الشيفرة (يكفي تبديل عنوان WebSocket).
مثال JavaScript
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
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())
ملاحظات النشر
- لا توجد مصادقة افتراضيًا. يربط
speech-serverالعنوان127.0.0.1ويثق بكلّ المتّصلين. ضعه خلف وكيل عكسي (Caddy أو nginx أو tailscale) إذا أردت إتاحته خارج localhost. - تُحمَّل النماذج بكسل. الطلب الأوّل لـ
/transcribeيُطلق تنزيل وتحميل ASR (نحو 700 MB، 30–60 ثانية). استخدم--preloadلإلغاء بدء التشغيل البارد على حساب إقلاع أبطأ. - لا يوجد بثّ تفريغ بعد. ينتظر
POST /transcribeملفّ WAV كاملًا مسبقًا. استخدم/v1/realtimeللبثّ. - Systemd / launchd. لا تُشحن وحدة خدمة اليوم. تعمل
nohup speech-server &البسيطة أو مشرف العمليّات المعتاد لديك جيّدًا.
الشيفرة المصدرية
- Sources/AudioServer — موجّه HTTP مبنيّ على Hummingbird، سجلّ نماذج كسول، معالج WebSocket لـ
/v1/realtime. - Sources/AudioServerCLI — نقطة دخول
@main، محلّل المعاملات. - المصدر الأصلي: مرجع OpenAI Realtime API.