المطورون / كلام إلى نص

بث تحويل الكلام إلى نص

WS wss://api.echonova.ai/v1/speech-to-text/stream

افتح اتصالًا واحدًا لكل مصدر صوت مباشر. أرسل صوت PCM16 أحادي القناة بترتيب little-endian في إطارات ثنائية. أرسل `end_segment` حين يتوقف المتحدث لتحصل على النص المفرّغ لذلك المقطع فورًا؛ ومن دونها يقطع الخادم مقطعًا كل 15 ثانية. كل نص مفرّغ نهائي: لا توجد نتائج مؤقتة أو جزئية. يُخصم رصيد كل مقطع بسعر تحويل الكلام إلى نص مباشرةً عند إرسال نصه المفرّغ.

زر «جرّبه» غير متاح هنا: لا يستطيع المتصفح إرسال مفتاح ⁦API⁩ عبر ⁦WebSocket⁩، لذا اتصل من خادمك. اقرأ دليل البث · جرّب التفريغ المباشر في البوابة

الاتصال

GET Upgrade: websocket

Authorization string مطلوب

كلمة ⁦Bearer⁩ ثم مسافة ثم مفتاح ⁦API⁩ الذي يبدأ بـ ⁦ek_⁩. أنشئ المفاتيح في البوابة من المطورون، ثم مفاتيح واجهة برمجة التطبيقات.

معاملات الاستعلام

sample_rate integer مطلوب

معدل عيّنات الصوت الذي ترسله، بوحدة Hz.

من 8000 إلى 48000
language string مطلوب

لغة الكلام، وهي مطلوبة. `ar` للعربية والكلام المختلط بين العربية والإنجليزية، و`en` للكلام الإنجليزي فقط.

ar أو en
encoding string اختياري

ترميز الصوت. لا يُقبل إلا PCM أحادي القناة بعمق 16 بت وبإشارة وترتيب little-endian.

pcm_s16leالافتراضي pcm_s16le

الرسائل التي ترسلها

audio إطار ثنائي

إطار ثنائي من صوت PCM16 أحادي القناة بترتيب little-endian، بمعدل `sample_rate` الذي اتصلت به. 64 KiB على الأكثر.

end_segment

فرّغ الصوت المرسَل منذ آخر مقطع الآن. أرسلها حين يتوقف المتحدث.

type string مطلوب
end_segment
{
  "type": "end_segment"
}

stop

فرّغ ما تبقى من الصوت، وأرسل آخر النصوص المفرّغة و`session.ended`، ثم أغلق الاتصال.

type string مطلوب
stop
{
  "type": "stop"
}

ping

رسالة اختيارية لإبقاء الاتصال حيًّا. يرد الخادم بـ `pong`.

type string مطلوب
ping
{
  "type": "ping"
}

الرسائل التي تستقبلها

session.started

تُرسل مرة واحدة بعد فتح الاتصال. ابدأ إرسال الصوت بعد وصولها.

type string مطلوب
session.started
sessionId string مطلوب

يعرّف هذه الجلسة في طلبات الدعم.

language string مطلوب

اللغة التي اتصلت بها.

ar أو en
sampleRate integer مطلوب

معدل العيّنات الذي يتوقعه الخادم، بوحدة Hz.

maxSessionSeconds integer مطلوب

عدد الثواني حتى ينهي الخادم الجلسة.

{
  "type": "session.started",
  "sessionId": "c8a1f2e4-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
  "language": "ar",
  "sampleRate": 16000,
  "maxSessionSeconds": 1500
}

transcript

النص المفرّغ النهائي لمقطع واحد. قد تصل المقاطع بعد إرسال `end_segment` بقليل؛ طابِقها بـ `segmentId`.

type string مطلوب
transcript
segmentId string مطلوب

يعرّف المقطع.

text string مطلوب

النص المفرّغ. يكون فارغًا إذا لم يحوِ المقطع كلامًا.

language string اختياري

اللغة المكتشفة، `ar` أو `en`.

durationSeconds number مطلوب

طول صوت المقطع بالثواني.

creditsConsumed number مطلوب

الرصيد المخصوم لهذا المقطع.

isFinal boolean مطلوب

دائمًا `true`: لا توجد نتائج مؤقتة.

true
{
  "type": "transcript",
  "segmentId": "6720f3a1c4b5d6e7f8a9b0c1",
  "text": "مرحبا، كيف يمكنني مساعدتك اليوم؟",
  "language": "ar",
  "durationSeconds": 2.4,
  "creditsConsumed": 8,
  "isFinal": true
}

session.ended

الرسالة الأخيرة. يغلق الخادم الاتصال بعدها مباشرة.

type string مطلوب
session.ended
reason string مطلوب

سبب انتهاء الجلسة.

stopped، max_duration، idle_timeout، insufficient_balance، spending_limit أو upstream_error
totalSeconds number مطلوب

ثواني الصوت التي فُرّغت في هذه الجلسة.

creditsConsumed number مطلوب

الرصيد المخصوم في هذه الجلسة.

{
  "type": "session.ended",
  "reason": "stopped",
  "totalSeconds": 42.6,
  "creditsConsumed": 142
}

error

حدث خطأ ما. يتبع الأخطاءَ القاتلة إطارُ إغلاق، أما غيرها فيبقي الاتصال مفتوحًا.

type string مطلوب
error
requestId string اختياري

في تحويل النص إلى كلام فقط: عملية التوليد التي فشلت.

code string مطلوب

سبب الخطأ بصيغة يقرؤها البرنامج.

invalid_message، frame_too_large، validation_failed، voice_not_found، insufficient_balance، spending_limit أو upstream_error
message string مطلوب

شرح الخطأ بصيغة يقرؤها الإنسان.

{
  "type": "error",
  "code": "validation_failed",
  "message": "text must be shorter than or equal to 500 characters"
}

pong

الرد على `ping`.

type string مطلوب
pong
{
  "type": "pong"
}

رموز الإغلاق

1000 إغلاق عادي
أرسلت `stop`، أو أغلقت الاتصال بنفسك.
1001 مغادرة
يُعاد تشغيل الخادم؛ أعد الاتصال.
1008 مخالفة السياسة
إطار ليس JSON صالحًا، أو بنوع مجهول، أو أكبر من 64 KiB.
1009 رسالة كبيرة جدًا
إطار أكبر من 1 MiB؛ يُغلق الاتصال دون رسالة خطأ.
1011 خطأ في الخادم
تحويل الكلام إلى نص: فشلت خدمة الكلام؛ أعد الاتصال وأعد المحاولة. أما في تحويل النص إلى كلام فيُرسل التوليد الفاشل رسالة `error` ويبقى الاتصال مفتوحًا.
4002 رصيد غير كافٍ
نفد رصيد مساحة العمل أثناء الجلسة.
4008 انتهاء المهلة
لم يصل أي إطار لمدة 120 ثانية، أو بلغت الجلسة أقصى مدتها.
4029 حد الإنفاق
بلغ المفتاح **حد الرصيد** الخاص به أثناء الجلسة.

الحدود

حجم الإطار
64 KiB لكل إطار ثنائي؛ ويُنصح بنحو 100 ms من الصوت في كل إطار.
طول المقطع
15 ثانية؛ يُقطع الكلام الأطول ويُفرَّغ على أجزاء.
مدة الجلسة
25 دقيقة؛ ثم ينهي الخادم الجلسة بالسبب `max_duration`.
مهلة الخمول
120 ثانية دون أي إطار.
البثوث المتزامنة
10 بثوث مفتوحة لكل مساحة عمل، تحويل الكلام إلى نص وتحويل النص إلى كلام معًا.
معدل الاتصال
100 محاولة اتصال في الدقيقة لكل مفتاح.

أخطاء المصافحة

ردود ⁦HTTP⁩ عادية على طلب الترقية، بالغلاف المعتاد. لا يُخصم أي رصيد. كل الأخطاء

WS /v1/speech-to-text/stream

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())
مثال على جلسة

  1. الخادم{"type":"session.started","sessionId":"c8a1f2e4-5b6d-4e7f-9a0b-1c2d3e4f5a6b","language":"ar","sampleRate":16000,"maxSessionSeconds":1500}
  2. أنتbinary: PCM16 frame
  3. أنتbinary: PCM16 frame
  4. أنت{"type":"end_segment"}
  5. الخادم{"type":"transcript","segmentId":"6720f3a1c4b5d6e7f8a9b0c1","text":"مرحبا، كيف يمكنني مساعدتك اليوم؟","language":"ar","durationSeconds":2.4,"creditsConsumed":8,"isFinal":true}
  6. أنت{"type":"stop"}
  7. الخادم{"type":"session.ended","reason":"stopped","totalSeconds":42.6,"creditsConsumed":142}
  8. الخادمclose 1000