أبقِ اتصالًا واحدًا مفتوحًا، وأرسل رسالة `synthesize` لكل جملة أو رد. يرد الخادم بـ `start`، ثم بإطارات PCM16 ثنائية، ثم بـ `done`. يُولَّد الصوت بضع ثوانٍ من الكلام في كل مرة، فيصل أول الصوت قبل نطق النص كله بوقت طويل. لا تجري إلا عملية توليد واحدة في كل مرة: أرسل `cancel`، أو `synthesize` جديدة، لإيقاف العملية الجارية عند المقاطعة. يُخصم رصيد كل `synthesize` بحسب عدد الأحرف عند بدئها؛ والتوليد الملغى يُخصم أيضًا.
توقفت عملية التوليد. لا يتبعها صوت آخر لهذا `requestId`.
typestringمطلوب
cancelled
requestIdstringمطلوب
{"type":"cancelled","requestId":"reply-1"}
error
حدث خطأ ما. يتبع الأخطاءَ القاتلة إطارُ إغلاق، أما غيرها فيبقي الاتصال مفتوحًا.
typestringمطلوب
error
requestIdstringاختياري
في تحويل النص إلى كلام فقط: عملية التوليد التي فشلت.
codestringمطلوب
سبب الخطأ بصيغة يقرؤها البرنامج.
invalid_message، frame_too_large، validation_failed، voice_not_found، insufficient_balance، spending_limit أو upstream_error
messagestringمطلوب
شرح الخطأ بصيغة يقرؤها الإنسان.
{"type":"error","code":"validation_failed","message":"text must be shorter than or equal to 500 characters"}
pong
الرد على `ping`.
typestringمطلوب
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 عادية على طلب الترقية، بالغلاف المعتاد. لا يُخصم أي رصيد.كل الأخطاء
متى يحدث.الترويسة `Authorization` مفقودة أو لا تبدأ بـ `Bearer ek_...`، أو الرمز لا يطابق أي مفتاح، أو المفتاح محذوف أو معطّل، أو حسابه مغلق.
الحل.أرسل `Authorization: Bearer ek_...` بمفتاح مفعّل من هذه البيئة.
متى يحدث.رصيد مساحة العمل فارغ.
الحل.أضف رصيدًا إلى مساحة العمل.
متى يحدث.المفتاح مقيّد، وتحويل النص إلى كلام ليس من خدماته.
الحل.عدّل المفتاح وامنحه تحويل النص إلى كلام، أو استخدم مفتاحًا آخر.
متى يحدث.حالة الاشتراك ليست نشطة ولا تجريبية.
الحل.جدّد الاشتراك.
متى يحدث.مساحة العمل التي يتبعها المفتاح ليس لديها اشتراك.
الحل.تحقّق من خطة مساحة العمل في البوابة.
متى يحدث.فتح المفتاح أكثر من 100 اتصال خلال 60 ثانية.
الحل.انتظر عدد الثواني المذكور في `Retry-After`، ثم أعد الاتصال.
متى يحدث.لدى مساحة العمل 10 بثوث مفتوحة بالفعل.
الحل.أغلق بثًّا، أو أعد استخدام اتصال واحد لعمليات توليد كثيرة.
متى يحدث.بلغ المفتاح **حد الرصيد** الخاص به لدورة الفوترة هذه.
الحل.ارفع الحد على المفتاح أو أزله، أو انتظر الدورة التالية.
متى يحدث.البث متوقف في هذه البيئة.
الحل.تواصل مع الدعم.
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())
مثال على جلسة
أنت{"type":"synthesize","requestId":"reply-1","text":"أهلاً بك، كيف يمكنني مساعدتك اليوم؟","voiceId":"66e5ef3a9c1d2b4f8a7e6c01"}