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

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

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

أبقِ اتصالًا واحدًا مفتوحًا، وأرسل رسالة `synthesize` لكل جملة أو رد. يرد الخادم بـ `start`، ثم بإطارات PCM16 ثنائية، ثم بـ `done`. يُولَّد الصوت بضع ثوانٍ من الكلام في كل مرة، فيصل أول الصوت قبل نطق النص كله بوقت طويل. لا تجري إلا عملية توليد واحدة في كل مرة: أرسل `cancel`، أو `synthesize` جديدة، لإيقاف العملية الجارية عند المقاطعة. يُخصم رصيد كل `synthesize` بحسب عدد الأحرف عند بدئها؛ والتوليد الملغى يُخصم أيضًا.

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

الاتصال

GET Upgrade: websocket

Authorization string مطلوب

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

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

synthesize

انطق هذا النص. تُلغي أي عملية توليد ما زالت جارية على هذا الاتصال.

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

معرّفك لعملية التوليد هذه، ويُعاد في `start` و`done` و`cancelled` و`error`. يُولَّد تلقائيًا إذا لم ترسله.

حتى 64 حرفًا
text string مطلوب

النص المراد نطقه، من 1 إلى 500 حرف.

من 1 إلى 500 حرفًا
voiceId string مطلوب

معرّف صوت من قائمة عرض الأصوات المتاحة. الصوت يحدد اللهجة.

speed number اختياري

سرعة نطق الصوت.

من 0.5 إلى 2الافتراضي 1
cfg number اختياري

مدى التزام الصوت بالنص. اتركه على القيمة الافتراضية إلا إذا كنت تضبط القيم.

من 1 إلى 5الافتراضي 2
numSteps integer اختياري

خطوات Diffusion. كلما زادت الخطوات كان التوليد أبطأ وأنظف.

16، 32، 48، 64 أو 128الافتراضي 48
tashkeel boolean اختياري

يضيف الحركات إلى النص العربي قبل نطقه. ينطبق على أصوات الفصحى فقط.

الافتراضي false
normalize boolean اختياري

ينطق الأرقام والتواريخ والأوقات والمبالغ والوحدات والروابط والبريد الإلكتروني كما ينطقها متحدث بلهجة الصوت.

الافتراضي true
{
  "type": "synthesize",
  "requestId": "reply-1",
  "text": "أهلاً بك، كيف يمكنني مساعدتك اليوم؟",
  "voiceId": "66e5ef3a9c1d2b4f8a7e6c01"
}

cancel

أوقف عملية التوليد الجارية. يرد الخادم بـ `cancelled` ولا يرسل بعدها صوتًا لها.

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

ping

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

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

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

start

تُرسل قبل أول إطار صوتي من عملية التوليد. استخدم `sampleRate` فيها لتشغيل الصوت.

type string مطلوب
start
requestId string مطلوب
sampleRate integer مطلوب

معدل عيّنات الإطارات الصوتية، بوحدة Hz.

encoding string مطلوب
pcm_s16le
channels integer مطلوب
1
{
  "type": "start",
  "requestId": "reply-1",
  "sampleRate": 24000,
  "encoding": "pcm_s16le",
  "channels": 1
}

audio إطار ثنائي

إطار ثنائي من صوت PCM16 أحادي القناة بترتيب little-endian، بمعدل `sampleRate` الوارد في `start`. شغّل الإطارات بترتيب وصولها.

done

أُرسل كل صوت عملية التوليد.

type string مطلوب
done
requestId string مطلوب
durationSeconds number مطلوب

طول الصوت المرسَل بالثواني.

characters integer مطلوب

عدد الأحرف المخصومة.

creditsConsumed number مطلوب

الرصيد المخصوم لعملية التوليد هذه.

{
  "type": "done",
  "requestId": "reply-1",
  "durationSeconds": 2.9,
  "characters": 36,
  "creditsConsumed": 36
}

cancelled

توقفت عملية التوليد. لا يتبعها صوت آخر لهذا `requestId`.

type string مطلوب
cancelled
requestId string مطلوب
{
  "type": "cancelled",
  "requestId": "reply-1"
}

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 حد الإنفاق
بلغ المفتاح **حد الرصيد** الخاص به أثناء الجلسة.

الحدود

طول النص
500 حرف لكل رسالة `synthesize`.
عمليات التوليد المتزامنة
عملية واحدة لكل اتصال؛ ورسالة `synthesize` جديدة تُلغي العملية الجارية.
مدة الجلسة
30 دقيقة.
مهلة الخمول
120 ثانية دون أي إطار.
البثوث المتزامنة
10 بثوث مفتوحة لكل مساحة عمل، تحويل الكلام إلى نص وتحويل النص إلى كلام معًا.
معدل الاتصال
100 محاولة اتصال في الدقيقة لكل مفتاح.

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

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

WS /v1/text-to-speech/stream

import asyncio
import json
import wave

import websockets  # pip install "websockets>=14"

URL = "wss://api.echonova.ai/v1/text-to-speech/stream"
API_KEY = "YOUR_API_KEY"


async def main():
    pcm = bytearray()
    rate = 24000
    async with websockets.connect(
        URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
    ) as ws:
        await ws.send(json.dumps({
            "type": "synthesize",
            "requestId": "reply-1",
            "text": "أهلاً بك، كيف يمكنني مساعدتك اليوم؟",
            "voiceId": "66e5ef3a9c1d2b4f8a7e6c01",
        }))
        while True:
            message = await ws.recv()
            if isinstance(message, bytes):  # a binary frame of PCM16
                pcm.extend(message)
                continue
            event = json.loads(message)
            if event["type"] == "start":
                rate = event["sampleRate"]
            elif event["type"] == "done":
                print("seconds:", event["durationSeconds"], "credits:", event["creditsConsumed"])
                break
            elif event["type"] in ("error", "cancelled"):
                raise SystemExit(f"{event['type']}: {event.get('message', '')}")

    with wave.open("speech.wav", "wb") as wav:
        wav.setnchannels(1)
        wav.setsampwidth(2)
        wav.setframerate(rate)
        wav.writeframes(bytes(pcm))


asyncio.run(main())
مثال على جلسة

  1. أنت{"type":"synthesize","requestId":"reply-1","text":"أهلاً بك، كيف يمكنني مساعدتك اليوم؟","voiceId":"66e5ef3a9c1d2b4f8a7e6c01"}
  2. الخادم{"type":"start","requestId":"reply-1","sampleRate":24000,"encoding":"pcm_s16le","channels":1}
  3. الخادمbinary: PCM16 frame
  4. الخادمbinary: PCM16 frame
  5. الخادم{"type":"done","requestId":"reply-1","durationSeconds":2.9,"characters":36,"creditsConsumed":36}