TTS API Python: تحضير النص للكلام باستخدام SDK
هذه الـ API لا تُرجع صوتًا. إنه نموذج نصي تستدعيه من Python باستخدام OpenAI SDK، وهو يناسب أن يكون أمام محرك صوت: يكتب النص، ينظفه ويقسمه إلى جمل يمكن لمرحلة الصوت الخاصة بك قراءتها بصوت عالٍ.
تم التحديث

ما يفعله هذا الـ API وما لا يفعله
إذا كنت تبحث عن دليل tts api python، يجب أن تكون نقطة واحدة واضحة قبل أي كود: هذه الخدمة لا تولّد الكلام. تعرض POST /v1/chat/completions وGET /v1/models لنموذج نصي مفتوح الأوزان واحد، ومع معرف النموذج uncensored. لا توجد نقطة نهاية للصوت، ولا قائمة أصوات، ولا ملف صوتي في أي استجابة. كما لا توجد مخرجات للصور أو الفيديو أو التضمين.
إذن أين يناسب هذا المشروع؟ في الجزء النصي من خط المعالجة. قبل أن يتمكن محرك الصوت من قراءة نص، يجب على شخص ما كتابته، وتوسيع الأرقام والاختصارات إلى كلمات منطوقة، وإزالة علامات التنسيق، وتقسيمها إلى أجزاء قصيرة بما يكفي لتوليفها دون توقفات طويلة. يقوم نموذج اللغة بهذا العمل بشكل جيد، ويمكنك استدعاؤه من Python باستخدام نفس SDK الذي قد تستخدمه بالفعل للخدمات المتوافقة مع OpenAI.
النموذج مُعدّ للإجابة بدون رفض المحتوى للاستخدام القانوني للبالغين، وهو أمر مهم للخيال وحوارات الشخصيات والسرد حيث قد يتوقف النموذج الأكثر تشددًا في منتصف المشهد. تنطبق قاعدة صارمة واحدة: لا محتوى جنسي يتضمن القاصرين.
يبني بقية هذا المقال وحدة Python صغيرة في أربع خطوات: استدعاء أولي، مقسّم جمل بالبث المتدفق، مُطبِّع للنص مع إخراج JSON، وغلاف إعادة محاولة يحترم حدود المعدل.
تثبيت SDK وإجراء استدعاء أولي
أنت بحاجة إلى Python 3.8 أو أحدث وحزمة openai الرسمية. أنشئ مفتاحًا على صفحة مفتاح API أولاً. يمكن للحسابات الجديدة المطالبة برصيد تجريبي مجاني بقيمة $0.50 هناك بنقرة واحدة، ولا حاجة لبطاقة ائتمان لذلك.
pip install openai
احتفظ بالمفتاح خارج الكود المصدري. قم بتصديره كمتغير بيئة واقرأه في الكود:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You write short narration scripts. Plain sentences only."},
{"role": "user", "content": "Write a 60-word intro for a podcast about night trains."},
],
temperature=0.8,
)
script = resp.choices[0].message.content
print(script)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
هذا هو التكامل بأكمله. يرسل لك الـ SDK Authorization: Bearer <key>. يحمل كل استجابة أيضًا رأس X-Balance-USD، لذا يمكنك قراءة رصيدك المتبقي من الاستجابة الخام إذا أردت تسجيله. تذكر صفحة الـ docs باقي الرؤوس والمعاملات الأخرى.
تمرير المعاملات مثل temperature، top_p، stop، seed والعقوبات كما هي. بالنسبة للسرد، فإن درجة حرارة بين 0.6 و0.9 هي نطاق بداية معقول، ويساعد الثابت seed عندما تريد إعادة إنتاج أداء أثناء ضبط الموجّهات.
بث النص وتقسيمه إلى جمل
تعمل محركات النطق عادةً جملة بجملة. إذا انتظرت انتهاء نص مكون من 900 كلمة، سينتظر المستمع أيضًا. مع stream: true ترسل الـ API الرموز عبر أحداث مُرسلة من الخادم (SSE)، ويمكنك تسليم كل جملة مكتملة إلى مرحلة الصوت بينما لا يزال الباقي يُكتب.
import os
import re
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
SENTENCE_END = re.compile(r"(?<=[.?])\s+")
def stream_sentences(topic):
stream = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Write spoken narration. One idea per sentence."},
{"role": "user", "content": topic},
],
stream=True,
)
buffer = ""
for chunk in stream:
if not chunk.choices:
continue # the final chunk can carry usage only
buffer += chunk.choices[0].delta.content or ""
parts = SENTENCE_END.split(buffer)
for sentence in parts[:-1]:
yield sentence.strip()
buffer = parts[-1]
if buffer.strip():
yield buffer.strip()
for line in stream_sentences("Explain how a tide table is read."):
print(line) # replace with: your_voice_engine.say(line)
تفاصيلان سهل تفويتهما. أولاً، تصل أرقام الاستخدام في نهاية البث، في قطعة قد تحتوي على قائمة choices فارغة، ولهذا السبب يتخطى الحلقة القطع بدون خيارات. ثانيًا، يحتفظ الطلب المتدفق بمكان التزامن الخاص به حتى ينتهي البث. إذا كان محرك الصوت الخاص بك بطيئًا وقمت بقراءة البث بشكل كسول، سيظل المكان مشغولاً. استخرج النص بسرعة، ضع الجمل في قائمة انتظار، ودع جانب الصوت يستهلك قائمة الانتظار بالسرعة الخاصة به.
التعبير النمطي أعلاه يقسم على نقطة أو علامة استفهام تليها مسافة. سيقسم بشكل خاطئ بعد الاختصارات مثل "Dr." أو "No."، وهو سبب آخر لتطبيع النص أولاً، كما يظهر القسم التالي.
تطبيع النص للأذن
يختلف النص المكتوب عن النص المنطوق. عبارة "اشترى الدكتور لي 3 نسخ في 3/4 للطبعة 2" سهلة القراءة وصعبة النطق. تتعامل بعض محركات الكلام مع بعض هذه الحالات، لكن النتائج تختلف حسب المحرك، لذا فإن تنظيف النص البرمجي قبل وصوله إلى مرحلة الصوت يزيل فئة من المفاجآت. اطلب من النموذج القيام بذلك بتعليمات صريحة:
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
NORMALIZE = (
"Rewrite the user's text so it can be read aloud. "
"Spell out numbers, currency, dates and abbreviations as words. "
"Remove markdown, URLs and emoji. Keep the meaning and the order. "
"Return only the rewritten text."
)
def normalize(text):
r = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": NORMALIZE},
{"role": "user", "content": text},
],
temperature=0.2,
)
return r.choices[0].message.content.strip()
print(normalize("Dr. Lee bought 3 copies on 3/4 for the 2nd edition."))
استخدم درجة حرارة منخفضة لهذه الخطوة. تريد أن يعطي الإدخال نفسه المخرجات نفسها، والإبداع فقط يعيق الطريق. راجع عينة من النتائج قبل تشغيل كتاب كامل من خلاله، وقرر كيف تريد التعامل مع العناصر الغامضة. التاريخ "3/4" هو المثال القياسي: اقرأه كـ 4 مارس أو 3 أبريل؟ ضع اختيارك في نظام الموجّه، لأن النموذج لا يمكنه تخمين منطقتك.
تكلفة هذه الخطوة
التطبيع رخيص. فقرة مكونة من 600 رمز داخلاً وحوالي 650 رمزًا خارجًا تكلف 600 × $0.15 / 1,000,000 = $0.00009 للإدخال، بالإضافة إلى 650 × $0.50 / 1,000,000 = $0.000325 للمخرجات، بمجموع $0.000415. هذا حوالي $0.42 لألف فقرة. يحتوي صفحة التسعير على الجدول الكامل وخيارات شحن الرصيد.
إرجاع أجزاء منظمة بوضع JSON
بعض محركات النطق تقبل إعدادات لكل جزء: طول التوقف، معدل الكلام، صوت مختلف لشخصية مختلفة. يمكنك أن تجعل النموذج ينتج هذه التعليمات كـ JSON وقراءتها في Python. اضبط response_format إلى {"type": "json_object"} ووصف الشكل أيضًا في الموجّه، لأن وضع JSON يضمن JSON صالحًا، وليس مخططك.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
prompt = (
"Split this scene into segments. Return JSON like "
'{"segments": [{"speaker": "narrator", "text": "...", "pause_ms": 300}]}. '
"Speakers are narrator, mara, or tom."
)
r = client.chat.completions.create(
model="uncensored",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": "Mara opens the door. Tom says he is late again. She sighs and lets him in."},
],
temperature=0.3,
)
data = json.loads(r.choices[0].message.content)
for seg in data["segments"]:
print(seg["speaker"], seg["pause_ms"], seg["text"])
تحقق مما يعود. تحقق من وجود segments، وأن كل عنصر لديه متحدث معروف، وأن pause_ms هو رقم في نطاق تقبله. إذا فشل التحقق، أعد المحاولة مرة واحدة بدرجة حرارة أقل قبل الاستسلام. تعامل مع مخرجات النموذج كمدخلات غير موثوقة، تمامًا كما تتعامل مع أي نص مستخدم.
أخطاء تكلف الوقت
تظهر بعض الأخطاء مرارًا وتكرارًا عندما يربط الناس نموذج لغة أمام محرك نطق.
- السماح للنموذج بإضافة تعليقات. تميل النماذج إلى قول "بالتأكيد، إليك النص البرمجي." سيتم نطق هذه الجملة بصوت عالٍ. أنهِ موجّه النظام الخاص بك بـ "أعد نص النص البرمجي فقط" واحذف السطر الأولي إذا كان ينتهي بنقطتين رأسيتين.
- الثقة في طول الجملة. تواجه بعض المحركات صعوبة مع الجمل الطويلة جدًا. أضف قاعدة مثل "لا توجد جملة أطول من 25 كلمة" إلى الموجّه، وقم بإنفاذها في Python عن طريق التقسيم على الفواصل عندما تكون الجملة طويلة جدًا.
- نسيان نافذة السياق. يتشارك الموجّه والإكمال 100,000 رمز. بالنسبة لفصل كتاب طويل، أرسله على أجزاء مع ملخص قصير للجزء السابق بدلاً من النص الكامل في كل مرة. هذا أيضًا يقلل من فاتورة الإدخال.
- إعادة استخدام عميل عالمي واحد عبر الخيوط دون حد. العميل آمن للمشاركة، لكن الخدمة ليست لا نهائية. حدد حدًا لمجموعة الاتصال الخاصة بك بناءً على التزامن المسموح به لمفتاحك.
- تثبيت المفتاح بشكل ثابت. استخدم متغير بيئة وأعد تدوير المفتاح إذا وُجد في مستودع الشيفرة.
عندما يبدو شيء ما خاطئًا، سجّل رأس X-Request-Id مع الموجّه الذي تسبب فيه. إذا احتجت إلى طلب المساعدة، فإن هذا المعرف هو أسرع طريقة للإشارة إلى مكالمة محددة، وتشرح الوثائق كيفية العثور عليه.
معالجة حدود المعدل وإعادة المحاولة
المفتاح الكامل يسمح بـ 8 طلبات متزامنة و300 طلب في الدقيقة. يسمح المفتاح التجريبي بـ 2 طلب متزامن و60 طلب في الدقيقة. عند تجاوز أي من الحدود، تحصل على HTTP 429، مع رمز الخطأ rate_limited لطلبات في الدقيقة أو concurrency لعدد كبير جدًا في وقت واحد. في حالات الحمل الزائد المؤقت، تجيب الـ API أيضًا بـ 429 وترسل Retry-After: 5.
تعيد الحزمة البرمجية (SDK) المحاولة تلقائيًا، لكن الغلاف الصريح يمنحك التحكم ويجعل السلوك ظاهرًا في السجلات. ادمجه مع مجموعة خيوط بحجم مفتاحك، ولن ترى 429 تقريبًا:
import os
import time
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
max_retries=0,
)
def ask(prompt, tries=5):
for attempt in range(tries):
try:
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
except RateLimitError as err:
wait = int(err.response.headers.get("Retry-After", 5))
time.sleep(wait * (attempt + 1))
raise RuntimeError("still rate limited after retries")
prompts = ["Narrate a sunrise in two sentences."] * 20
with ThreadPoolExecutor(max_workers=8) as pool: # use 2 on a trial key
results = list(pool.map(ask, prompts))
print(len(results))
الطلبات الفاشلة أو المرفوضة لا تُحسب، لذا فإن إعادة المحاولة بعد الخطأ لا تكلفك عن المحاولة الفاشلة. يُحسب الطلب الذي يكتمل بشكل طبيعي، لذا تجنب إعادة المحاولة غير المشروطة على المكالمات البطيئة ولكن الناجحة.
تسليم النص إلى محرك النطق
الخطوة الأخيرة تقع خارج هذه الواجهة. تتوفر في Python عدة طرق لإنتاج الصوت من النص: نماذج كلام مفتوحة المصدر يمكنك تشغيلها بنفسك، وخدمات نطق سطح المكتب المدمجة في نظام التشغيل، وواجهات برمجة تطبيقات (API) للنطق المستضافة من شركات أخرى. لكل منها ترخيصها الخاص وجودتها وسعرها، لذا تحقق من وثائقها وشروطها الحالية قبل الاختيار. لا يوفر هذا الموقع هذا الجزء.
يُحافظ الحدّ النظيف على سهولة تغيير شيفرتك. اكتب دالة واحدة بالتوقيع speak(text: str) -> None واحتفظ بكل تفاصيل محرك معين داخلها. تستدعي سلسلة النص المذكورة أعلاه هذه الدالة لكل جملة أو جزء. عند تبديل المحركات، لا يتغير شيء آخر.
إذا كنت لا تزال تختار بين تجربة وإعداد مدفوع، فالأرقام بسيطة: تدفع $0.15 لكل مليون رمز إدخال و$0.50 لكل مليون رمز إخراج، من رصيد مسبق الدفع لا ينتهي، بدون اشتراك. عمليات الشحن تتراوح من $20 إلى $500 ويمكن دفعها بالعملات المشفرة فقط (USDT على TRC20، USDC على Base).
المزيد من الأدلة
أسئلة وأجوبة
هل تُرجع هذه الـ API ملفات صوتية؟
لا. تُرجع النص فقط. استخدمها لكتابة وتنظيف وهيكلة النصوص، ثم مرر النص إلى محرك نطق منفصل.
ما هي حزمة Python التي يجب استخدامها؟
الحزمة الرسمية openai. اضبط base_url على https://api.xtts.cc/v1, ومرر مفتاحك، واستخدم معرف النموذج uncensored.
هل يمكنني استقبال الاستجابات المتدفقة في Python؟
نعم. مرر stream=True وتكرّر عبر القطع. يُبلغ عن الاستخدام في نهاية التدفق، ويحتفظ الطلب بمكان التزامن الخاص به حتى ينتهي التدفق.
ماذا يحدث عند الوصول إلى حدّ المعدل؟
تحصل على HTTP 429 مع الرمز rate_limited أو concurrency. انتظر قيمة Retry-After إذا كانت موجودة وأعد المحاولة، واحتفظ بعدد عمالك عند أو أقل من 8 على مفتاح كامل أو 2 على مفتاح تجريبي.
مفتاحك على بُعد استمارة واحدة
أنشئ حساباً، وانسخ المفتاح، ثم غيّر URL الأساسي. هذا هو الإعداد الكامل.