audio إطار ثنائي
إطار ثنائي من صوت PCM16 أحادي القناة بترتيب little-endian، بمعدل `sample_rate` الذي اتصلت به. 64 KiB على الأكثر.
المطورون / كلام إلى نص
wss://api.echonova.ai/v1/speech-to-text/streamافتح اتصالًا واحدًا لكل مصدر صوت مباشر. أرسل صوت PCM16 أحادي القناة بترتيب little-endian في إطارات ثنائية. أرسل `end_segment` حين يتوقف المتحدث لتحصل على النص المفرّغ لذلك المقطع فورًا؛ ومن دونها يقطع الخادم مقطعًا كل 15 ثانية. كل نص مفرّغ نهائي: لا توجد نتائج مؤقتة أو جزئية. يُخصم رصيد كل مقطع بسعر تحويل الكلام إلى نص مباشرةً عند إرسال نصه المفرّغ.
زر «جرّبه» غير متاح هنا: لا يستطيع المتصفح إرسال مفتاح API عبر WebSocket، لذا اتصل من خادمك. اقرأ دليل البث · جرّب التفريغ المباشر في البوابة
GET Upgrade: websocket
كلمة Bearer ثم مسافة ثم مفتاح API الذي يبدأ بـ ek_. أنشئ المفاتيح في البوابة من المطورون، ثم مفاتيح واجهة برمجة التطبيقات.
معدل عيّنات الصوت الذي ترسله، بوحدة Hz.
لغة الكلام، وهي مطلوبة. `ar` للعربية والكلام المختلط بين العربية والإنجليزية، و`en` للكلام الإنجليزي فقط.
ترميز الصوت. لا يُقبل إلا PCM أحادي القناة بعمق 16 بت وبإشارة وترتيب little-endian.
audio إطار ثنائيإطار ثنائي من صوت PCM16 أحادي القناة بترتيب little-endian، بمعدل `sample_rate` الذي اتصلت به. 64 KiB على الأكثر.
end_segmentفرّغ الصوت المرسَل منذ آخر مقطع الآن. أرسلها حين يتوقف المتحدث.
{
"type": "end_segment"
}stopفرّغ ما تبقى من الصوت، وأرسل آخر النصوص المفرّغة و`session.ended`، ثم أغلق الاتصال.
{
"type": "stop"
}pingرسالة اختيارية لإبقاء الاتصال حيًّا. يرد الخادم بـ `pong`.
{
"type": "ping"
}session.startedتُرسل مرة واحدة بعد فتح الاتصال. ابدأ إرسال الصوت بعد وصولها.
يعرّف هذه الجلسة في طلبات الدعم.
اللغة التي اتصلت بها.
معدل العيّنات الذي يتوقعه الخادم، بوحدة Hz.
عدد الثواني حتى ينهي الخادم الجلسة.
{
"type": "session.started",
"sessionId": "c8a1f2e4-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
"language": "ar",
"sampleRate": 16000,
"maxSessionSeconds": 1500
}transcriptالنص المفرّغ النهائي لمقطع واحد. قد تصل المقاطع بعد إرسال `end_segment` بقليل؛ طابِقها بـ `segmentId`.
يعرّف المقطع.
النص المفرّغ. يكون فارغًا إذا لم يحوِ المقطع كلامًا.
اللغة المكتشفة، `ar` أو `en`.
طول صوت المقطع بالثواني.
الرصيد المخصوم لهذا المقطع.
دائمًا `true`: لا توجد نتائج مؤقتة.
{
"type": "transcript",
"segmentId": "6720f3a1c4b5d6e7f8a9b0c1",
"text": "مرحبا، كيف يمكنني مساعدتك اليوم؟",
"language": "ar",
"durationSeconds": 2.4,
"creditsConsumed": 8,
"isFinal": true
}session.endedالرسالة الأخيرة. يغلق الخادم الاتصال بعدها مباشرة.
سبب انتهاء الجلسة.
ثواني الصوت التي فُرّغت في هذه الجلسة.
الرصيد المخصوم في هذه الجلسة.
{
"type": "session.ended",
"reason": "stopped",
"totalSeconds": 42.6,
"creditsConsumed": 142
}errorحدث خطأ ما. يتبع الأخطاءَ القاتلة إطارُ إغلاق، أما غيرها فيبقي الاتصال مفتوحًا.
في تحويل النص إلى كلام فقط: عملية التوليد التي فشلت.
سبب الخطأ بصيغة يقرؤها البرنامج.
شرح الخطأ بصيغة يقرؤها الإنسان.
{
"type": "error",
"code": "validation_failed",
"message": "text must be shorter than or equal to 500 characters"
}pongالرد على `ping`.
{
"type": "pong"
}ردود HTTP عادية على طلب الترقية، بالغلاف المعتاد. لا يُخصم أي رصيد. كل الأخطاء
متى يحدث. `sample_rate` مفقود، أو ليس عددًا صحيحًا، أو خارج النطاق.
الحل. اتصل مع `?sample_rate=` مضبوطًا على معدل عيّنات صوتك.
متى يحدث. `language` غير موجود أو بقيمة غير `ar` أو `en`.
الحل. اتصل مع `?language=` بقيمة `ar` أو `en`.
متى يحدث. `encoding` بقيمة غير مسموحة.
الحل. احذف `encoding`، أو أرسل `pcm_s16le`.
متى يحدث. الترويسة `Authorization` مفقودة أو لا تبدأ بـ `Bearer ek_...`، أو الرمز لا يطابق أي مفتاح، أو المفتاح محذوف أو معطّل، أو حسابه مغلق.
الحل. أرسل `Authorization: Bearer ek_...` بمفتاح مفعّل من هذه البيئة.
متى يحدث. رصيد مساحة العمل فارغ.
الحل. أضف رصيدًا إلى مساحة العمل.
متى يحدث. المفتاح مقيّد، وتحويل الكلام إلى نص ليس من خدماته.
الحل. عدّل المفتاح وامنحه تحويل الكلام إلى نص، أو استخدم مفتاحًا آخر.
متى يحدث. حالة الاشتراك ليست نشطة ولا تجريبية.
الحل. جدّد الاشتراك.
متى يحدث. مساحة العمل التي يتبعها المفتاح ليس لديها اشتراك.
الحل. تحقّق من خطة مساحة العمل في البوابة.
متى يحدث. فتح المفتاح أكثر من 100 اتصال خلال 60 ثانية.
الحل. انتظر عدد الثواني المذكور في `Retry-After`، ثم أعد الاتصال.
متى يحدث. لدى مساحة العمل 10 بثوث مفتوحة بالفعل.
الحل. أغلق بثًّا، أو أعد استخدام اتصال واحد لعمليات توليد كثيرة.
متى يحدث. بلغ المفتاح **حد الرصيد** الخاص به لدورة الفوترة هذه.
الحل. ارفع الحد على المفتاح أو أزله، أو انتظر الدورة التالية.
متى يحدث. البث متوقف في هذه البيئة.
الحل. تواصل مع الدعم.
import asyncio
import json
import wave
import websockets # pip install "websockets>=14"
URL = "wss://api.echonova.ai/v1/speech-to-text/stream"
API_KEY = "YOUR_API_KEY"
async def main():
# A 16-bit mono PCM WAV file; its header gives the sample rate.
with wave.open("audio.wav", "rb") as wav:
if wav.getsampwidth() != 2 or wav.getnchannels() != 1:
raise SystemExit("audio.wav must be 16-bit mono PCM")
rate = wav.getframerate()
pcm = wav.readframes(wav.getnframes())
async with websockets.connect(
f"{URL}?sample_rate={rate}&language=ar",
additional_headers={"Authorization": f"Bearer {API_KEY}"},
) as ws:
print(json.loads(await ws.recv())) # session.started
async def send_audio():
step = rate // 10 * 2 # 100 ms of 16-bit samples, in bytes
for i in range(0, len(pcm), step):
await ws.send(pcm[i : i + step]) # bytes: a binary frame
await asyncio.sleep(0.1) # real time, like a live source
await ws.send(json.dumps({"type": "end_segment"}))
await ws.send(json.dumps({"type": "stop"}))
sender = asyncio.create_task(send_audio())
async for raw in ws: # ends when the server closes after session.ended
message = json.loads(raw)
if message["type"] == "transcript":
print(message["text"])
elif message["type"] == "error":
print("error:", message["code"], message["message"])
elif message["type"] == "session.ended":
print("ended:", message["reason"], "credits:", message["creditsConsumed"])
await sender
asyncio.run(main()){"type":"session.started","sessionId":"c8a1f2e4-5b6d-4e7f-9a0b-1c2d3e4f5a6b","language":"ar","sampleRate":16000,"maxSessionSeconds":1500}binary: PCM16 framebinary: PCM16 frame{"type":"end_segment"}{"type":"transcript","segmentId":"6720f3a1c4b5d6e7f8a9b0c1","text":"مرحبا، كيف يمكنني مساعدتك اليوم؟","language":"ar","durationSeconds":2.4,"creditsConsumed":8,"isFinal":true}{"type":"stop"}{"type":"session.ended","reason":"stopped","totalSeconds":42.6,"creditsConsumed":142}close 1000