> ## Documentation Index
> Fetch the complete documentation index at: https://docs.venice.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# سرد المقالات باستخدام تحويل النص إلى كلام

> حوّل أي مقالة على الويب إلى ملف صوتي مسرود باستخدام تحويل النص إلى كلام في Venice، ويشمل ذلك اختيار الصوت، وحدَّ 4096 حرفًا، ودمج الصوت بلا فجوات، والبثّ.

إجراء نداء واحد إلى `/audio/speech` سهل. سرد مقالة حقيقية هو حيث تظهر المشكلات المثيرة للاهتمام: تقبل نقطة النهاية 4096 حرفًا كحد أقصى لكل طلب، وكل صوت ينتمي إلى نموذج معيّن، وصيغ الصوت تختلف من نموذج إلى آخر، والنص المكتوب ليُقرأ لا يشبه إطلاقًا النص المكتوب ليُسمع.

في هذا الدرس سنعالج الأربعة كلها. النتيجة سكربت يُحوِّل عنوان URL إلى ملف صوتي واحد:

```bash theme={"system"}
python article_to_audio.py https://docs.venice.ai/overview/privacy
```

سنقوم بما يلي:

1. اختيار نموذج وصوت يناسبان السرد الطويل
2. إجراء طلب كلام واحد وحفظ الصوت
3. تقسيم مقالة طويلة إلى أجزاء تتّسع لحدّ الأحرف
4. دمج الأجزاء المُولَّدة في ملف واحد دون طبقات مسموعة
5. إعادة صياغة المقالة إلى نص يستحق الاستماع
6. جمع القطع معًا، ثم إلقاء نظرة على البثّ للاستخدام التفاعلي

## الإعداد

تحتاج إلى Python 3.9 أو أحدث، وحزمة `requests`، ومفتاح Venice API.

```bash theme={"system"}
pip install requests
export VENICE_API_KEY="your-api-key-here"
```

## 1. اختر نموذجًا وصوتًا

الأصوات تنتمي إلى نماذج. إرسال صوت من عائلة إلى نموذج من عائلة أخرى هو أشيع الأخطاء الأولى، لذا ابدأ بسرد ما يقبله كل نموذج فعليًا:

```bash theme={"system"}
curl "https://api.venice.ai/api/v1/models?type=tts" \
  -H "Authorization: Bearer $VENICE_API_KEY" |
  jq -r '.data[] | "\(.id)  formats=\(.model_spec.supported_formats)  voices=\(.model_spec.voices | length)"'
```

```
tts-kokoro  formats=["mp3","opus","aac","flac","wav","pcm"]  voices=54
tts-qwen3-1-7b  formats=["mp3"]  voices=9
tts-xai-v1  formats=["mp3","wav","pcm"]  voices=26
tts-inworld-1-5-max  formats=["wav"]  voices=14
tts-chatterbox-hd  formats=["wav"]  voices=9
tts-orpheus  formats=["wav"]  voices=8
tts-elevenlabs-turbo-v2-5  formats=["mp3"]  voices=21
tts-minimax-speech-02-hd  formats=["mp3","pcm","flac"]  voices=15
tts-gemini-3-1-flash  formats=["mp3","opus","wav"]  voices=30
tts-gradium-v1  formats=["wav","pcm","opus"]  voices=12
```

`model_spec.voices` هي القائمة المعتمدة للأصوات في نموذج، ويخبرك `supported_formats` بقيم `response_format` التي يقبلها. احذف `| length` من الاستعلام لطباعة أسماء الأصوات نفسها.

سنستخدم `tts-xai-v1` مع الصوت `eve`. يدعم `pcm`، وهو ما يجعل دمج الأجزاء سهلًا في القسم 4.

<Note>
  سرعة التوليد تتفاوت بين نماذج TTS بشكل أكبر بكثير من جودة المخرجات، والفجوة كبيرة بما يكفي لتغيير معماريتك. قِس زمن طلب واقعي مقابل نموذجين أو ثلاثة قبل أن تلتزم. جزء قد يُعيده نموذج في ثوانٍ قليلة قد يستغرق آخر عدة دقائق.
</Note>

## 2. إجراء طلب واحد

جسم الاستجابة صوت خام لا JSON، لذلك اكتب البايتات مباشرةً إلى ملف.

<CodeGroup>
  ```python Python theme={"system"}
  import os
  from pathlib import Path

  import requests

  response = requests.post(
      "https://api.venice.ai/api/v1/audio/speech",
      headers={
          "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "model": "tts-xai-v1",
          "voice": "eve",
          "input": "Hello from Venice.",
          "response_format": "mp3",
      },
      timeout=300,
  )

  response.raise_for_status()
  Path("hello.mp3").write_bytes(response.content)
  ```

  ```javascript Node.js theme={"system"}
  import { writeFile } from "node:fs/promises";

  const response = await fetch("https://api.venice.ai/api/v1/audio/speech", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VENICE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "tts-xai-v1",
      voice: "eve",
      input: "Hello from Venice.",
      response_format: "mp3",
    }),
  });

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  await writeFile("hello.mp3", Buffer.from(await response.arrayBuffer()));
  ```

  ```bash cURL theme={"system"}
  curl https://api.venice.ai/api/v1/audio/speech \
    -H "Authorization: Bearer $VENICE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "tts-xai-v1",
      "voice": "eve",
      "input": "Hello from Venice.",
      "response_format": "mp3"
    }' \
    --output hello.mp3
  ```
</CodeGroup>

يُرفض المزج غير المتوافق قبل توليد أيّ صوت، والخطأ يخبرك بما كان سيعمل بدلًا منه:

```json theme={"system"}
{"error":"Voice \"eve\" is not supported by model \"tts-kokoro\". Try using a supported voice: af_alloy, af_aoede, af_bella, af_heart, af_jadzia, ...."}
```

```json theme={"system"}
{"error":"response_format \"wav\" is not supported by model \"tts-elevenlabs-turbo-v2-5\". Supported formats: mp3."}
```

## 3. تقسيم النص عند حدّ 4096 حرفًا

يقبل حقل `input` 4096 حرفًا كحد أقصى. النص الأطول يُرفض بالكامل بدلًا من اقتطاعه بصمت:

```json theme={"system"}
{"error":"Invalid request parameters","issues":[{"code":"too_big","maximum":4096,"path":["input"]}]}
```

لذلك نقسّم المقالة أولًا. التقسيم عند حدود الجُمل مهم، لأن جزءًا ينتهي في منتصف جملة يُنتج تعثّرًا مسموعًا عند نقطة الدمج. أنشئ `narrate.py`:

```python theme={"system"}
from __future__ import annotations

import os
import re
import sys
import wave
from concurrent.futures import ThreadPoolExecutor

import requests

BASE_URL = "https://api.venice.ai/api/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
    "Content-Type": "application/json",
}

MODEL = "tts-xai-v1"
VOICE = "eve"
SAMPLE_RATE = 24000  # tts-xai-v1 returns 24 kHz mono signed 16-bit PCM.

SENTENCE_END = re.compile(r"(?<=[.!?])\s+")


def split_into_chunks(text: str, max_chars: int = 1500) -> list[str]:
    """Split text on sentence boundaries into chunks under the 4096-character cap."""
    chunks: list[str] = []
    current = ""

    for sentence in SENTENCE_END.split(text.strip()):
        if not sentence:
            continue
        if len(sentence) > max_chars:
            raise ValueError(f"Sentence longer than {max_chars} characters: {sentence[:80]}...")
        if len(current) + len(sentence) + 1 > max_chars:
            chunks.append(current)
            current = sentence
        else:
            current = f"{current} {sentence}" if current else sentence

    if current:
        chunks.append(current)
    return chunks
```

على نصٍّ من 5362 حرفًا يُنتج هذا أربعة أجزاء، ينتهي كلٌّ منها عند جملة:

```python theme={"system"}
chunks = split_into_chunks(script)
print(f"len(chunks) = {len(chunks)}")
for index, chunk in enumerate(chunks):
    print(f"chunk {index}: {len(chunk)} chars")
```

```
len(chunks) = 4
chunk 0: 1428 chars
chunk 1: 1410 chars
chunk 2: 1489 chars
chunk 3: 1015 chars
```

قيمة `max_chars` الافتراضية 1500 لا شيء قريب من سقف 4096، وذلك بقصد. يزداد وقت التوليد مع طول الإدخال، لذلك تعود الأجزاء الأصغر أسرع، ولأنها تعمل بالتوازي فإنها تُنهي المهمة كاملة أسرع. كما تجعل إعادة المحاولة رخيصة عندما يفشل أحد الطلبات.

## 4. دمج الأجزاء في ملف واحد

ربط صوتٍ مُشفَّر مثل MP3 غير موثوق، لأن كل جزء يحمل ترويسات إطاراته الخاصة. طلب `pcm` يتجاوز المشكلة كليًا. PCM عيّنات خام دون حاوية، فالدمج مجرد إلحاق بايتات، ووحدة `wave` في مكتبة Python القياسية تكتب لنا الترويسة.

```python theme={"system"}
def synthesize(text: str, speed: float = 1.0) -> bytes:
    """Return raw PCM audio for one chunk."""
    response = requests.post(
        f"{BASE_URL}/audio/speech",
        headers=HEADERS,
        json={
            "model": MODEL,
            "voice": VOICE,
            "input": text,
            "response_format": "pcm",
            "speed": speed,
        },
        timeout=300,
    )
    if response.status_code != 200:
        raise RuntimeError(f"TTS failed ({response.status_code}): {response.text}")
    return response.content


def narrate(text: str, out_path: str) -> str:
    chunks = split_into_chunks(text)
    print(f"Synthesizing {len(chunks)} chunks", file=sys.stderr)

    with ThreadPoolExecutor(max_workers=4) as pool:
        audio = list(pool.map(synthesize, chunks))

    with wave.open(out_path, "wb") as output:
        output.setnchannels(1)
        output.setsampwidth(2)
        output.setframerate(SAMPLE_RATE)
        for part in audio:
            output.writeframes(part)

    seconds = sum(len(part) for part in audio) / 2 / SAMPLE_RATE
    print(f"Wrote {out_path} ({seconds:.1f}s of audio)", file=sys.stderr)
    return out_path
```

جزء واحد يعود بسرعة نسبةً إلى ما يحويه من صوت:

```python theme={"system"}
pcm = synthesize(chunks[0])
print(f"bytes   = {len(pcm)}")
print(f"audio   = {len(pcm) / 2 / SAMPLE_RATE:.1f}s")
```

```
bytes   = 4269376
audio   = 88.9s
```

استغرق هذا الطلب نحو 13 ثانية لإنتاج 89 ثانية من الكلام. تشغيل الأجزاء الأربعة على التوازي هو ما يُبقي المجموع معقولًا: استغرق السرد الكامل أدناه 15 ثانية من وقت الساعة.

`ThreadPoolExecutor.map` يُعيد النتائج بالترتيب الذي أُرسلت به المدخلات، لذلك تصل الأجزاء بترتيب القراءة رغم أنها وُلِّدت في الوقت نفسه.

<Warning>
  PCM الخام لا يحمل معدّل عيّنة، لذلك عليك أن تُوفّر المعدّل الصحيح عند كتابة ترويسة WAV، وهو خاصٌّ بكل نموذج. `tts-xai-v1` يُعيد 24 كيلوهرتز بينما `tts-gradium-v1` يُعيد 48 كيلوهرتز. إن أخطأت التخمين، يُشغَّل السرد بسرعة وطبقة صوت خاطئتين.
</Warning>

لمعرفة المعدّل لأي نموذج، اطلب مقطعًا قصيرًا واحدًا بصيغة `wav` واقرأ الترويسة التي يعود بها:

```python theme={"system"}
import wave

response = requests.post(
    f"{BASE_URL}/audio/speech",
    headers=HEADERS,
    json={"model": MODEL, "voice": VOICE, "input": "Probe.", "response_format": "wav"},
    timeout=300,
)
response.raise_for_status()
with open("probe.wav", "wb") as handle:
    handle.write(response.content)

with wave.open("probe.wav") as probe:
    print(probe.getframerate(), probe.getnchannels(), probe.getsampwidth())
```

```
24000 1 2
```

## 5. إعداد نصّ يبدو مناسبًا للاستماع

قراءة Markdown مستخرَج حرفيًا شبه غير قابلة للاستماع. عناوين URL أوضح مثال. نموذج الكلام يهجّئها حرفًا حرفًا، فيخرج `https://docs.venice.ai/llms.txt` هكذا:

> h t t p s نقطتان مائلة مائلة docs نقطة venice نقطة a i l l m s نقطة t x t

تسبّب العناوين وعلامات النقاط والجداول وكتل الشيفرة نُسخًا أصغر من المشكلة نفسها. بدلًا من محاربة Markdown بالتعبيرات النمطية، يمكننا أن نطلب من نموذج دردشة إعادة صياغة المقالة كنصٍّ مُعدّ للنطق. أنشئ `article_to_audio.py`:

```python theme={"system"}
from __future__ import annotations

import os
import re
import sys

import requests

from narrate import narrate

BASE_URL = "https://api.venice.ai/api/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['VENICE_API_KEY']}",
    "Content-Type": "application/json",
}

URL_PATTERN = re.compile(r"https?://\S+|www\.\S+")


def scrape(url: str) -> str:
    response = requests.post(
        f"{BASE_URL}/augment/scrape", headers=HEADERS, json={"url": url}, timeout=120
    )
    response.raise_for_status()
    return response.json()["content"]


def write_script(markdown: str, minutes: int = 6) -> str:
    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers=HEADERS,
        json={
            "model": "zai-org-glm-5-1",
            "messages": [
                {
                    "role": "system",
                    "content": (
                        "You rewrite articles as scripts to be read aloud. Output plain prose only: "
                        "no Markdown, no headings, no bullet points, no URLs, no code, no emoji. "
                        "Spell out abbreviations and numbers the way a narrator would say them. "
                        "Use short sentences with clear punctuation so speech synthesis paces well."
                    ),
                },
                {
                    "role": "user",
                    "content": f"Rewrite this article as a {minutes}-minute spoken summary.\n\n{markdown[:20000]}",
                },
            ],
            "temperature": 0.4,
        },
        timeout=300,
    )
    response.raise_for_status()
    script = response.json()["choices"][0]["message"]["content"]
    return URL_PATTERN.sub("", script).strip()
```

يبقى استبدال `URL_PATTERN` كشبكة أمان للرابط العَرضي الذي قد يتركه النموذج.

## 6. جمع الأجزاء معًا

نقطة الدخول تستخرج، وتكتب النص، وتحفظه، وتسرده:

```python theme={"system"}
if __name__ == "__main__":
    url = sys.argv[1]

    print("Scraping", url, file=sys.stderr)
    markdown = scrape(url)

    print(f"Writing script from {len(markdown)} characters of Markdown", file=sys.stderr)
    script = write_script(markdown)

    with open("script.txt", "w") as handle:
        handle.write(script)

    narrate(script, "article.wav")
```

حفظ `script.txt` بجانب الصوت يستحق السطرين. حين يبدو سردٌ خاطئًا، يُظهر النصُّ في الغالب سبب ذلك، ويمكنك إصلاحه دون دفع تكلفة توليد جديدة.

```bash theme={"system"}
python article_to_audio.py https://docs.venice.ai/overview/privacy
```

```
Scraping https://docs.venice.ai/overview/privacy
Writing script from 7582 characters of Markdown
Synthesizing 4 chunks
Wrote article.wav (355.0s of audio)
```

سردٌ قرابة ست دقائق، أُنتج في نحو خمس عشرة ثانية. يفتتح النص الآن بنثرٍ لا بأثاث تصفّح:

> Venice مبنيّ على مبدأ بسيط لكنه قوي. خصوصية المستخدم أولًا. تنبع بنية المنصة بكاملها من هذا الالتزام الفلسفي.

<Tip>
  للتحقق من سرد دون الاستماع إليه كاملًا، أعِد إرسال الصوت عبر [`/audio/transcriptions`](/guides/media/speech-to-text) وقارن التفريغ بملف `script.txt`. تفريغ آخر عشرين ثانية طريقة سريعة للتأكد من دمج الأجزاء بالترتيب الصحيح، ويكشف الأجزاء المفقودة وعناوين URL المُهجّاة في ثوانٍ.
</Tip>

## البثّ للاستخدام التفاعلي

السرد الدفعي يُحسِّن الوقت الإجمالي. أما واجهة الصوت التفاعلية فأولويتها معاكسة، وهي إخراج أول صوت بأسرع وقت ممكن. تعيين `streaming: true` يُعيد الجسم جملةً بجملة أثناء التوليد، بحيث يمكن أن يبدأ التشغيل بعد ثانية تقريبًا بدلًا من انتظار المقطع الكامل.

```python theme={"system"}
import time

import requests

start = time.time()
first_byte = None

with requests.post(
    "https://api.venice.ai/api/v1/audio/speech",
    headers=HEADERS,
    json={
        "model": "tts-xai-v1",
        "voice": "eve",
        "input": "Streaming returns audio while the rest is still being generated.",
        "response_format": "mp3",
        "streaming": True,
    },
    stream=True,
    timeout=300,
) as response:
    response.raise_for_status()
    with open("streamed.mp3", "wb") as audio:
        for chunk in response.iter_content(chunk_size=4096):
            if first_byte is None:
                first_byte = time.time() - start
            audio.write(chunk)

print(f"first byte: {first_byte:.2f}s   complete: {time.time() - start:.2f}s")
```

```
first byte: 0.85s   complete: 1.43s
```

فضّل `pcm` على `mp3` عندما تُغذّي به مباشرةً Web Audio API في المتصفح أو جهاز صوت، لأنه لا يحتاج إلى خطوة فك تشفير.

## خيارات الطلب التي تستحق المعرفة

| المعامِل      | ملاحظات                                                                                                                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `speed`       | يقبل من `0.25` إلى `4.0`، والافتراضي `1.0`. ابقَ تقريبًا بين `0.8` و`1.3` للسرد. خارج ذلك يتوقف الأداء عن أن يبدو طبيعيًا.                                                                 |
| `language`    | تلميح اختياري تختلف الصيغة المقبولة له بحسب النموذج. xAI وElevenLabs يقبلان رموز ISO 639-1 مثل `en`، بينما يقبل Qwen 3 وMiniMax الأسماء الكاملة مثل `English`. القيم غير المدعومة تُتجاهل. |
| `prompt`      | تلميح للأسلوب والانفعال، حتى 500 حرف، ويحترمه حاليًا نماذج Qwen 3 فقط. في العائلات الأخرى يحمل اختيار الصوت الطابع.                                                                        |
| `temperature` | النطاق `0` إلى `2`، ومدعوم في Qwen 3 وOrpheus وChatterbox HD. ارفعه لتنويعٍ أكبر بين الأخذات.                                                                                              |

## الأخطاء

| الحالة         | السبب                     | الحل                                             |
| -------------- | ------------------------- | ------------------------------------------------ |
| `400`          | `input` يتجاوز 4096 حرفًا | قسّم النص كما في القسم 3                         |
| `400`          | صوت غير صالح للنموذج      | استخدم صوتًا من `model_spec.voices` لذلك النموذج |
| `400`          | صيغة لا يدعمها النموذج    | راجع `model_spec.supported_formats`              |
| `401`          | مفتاح مفقود أو غير صالح   | تأكد من ترويسة `Authorization: Bearer`           |
| `402`          | الرصيد غير كافٍ           | اشحن الحساب                                      |
| `429`          | تحديد المعدّل             | قلّل `max_workers` وأعد المحاولة مع تأخير تصاعدي |
| `500` أو `503` | فشل في السعة أو الاستدلال | أعد محاولة الجزء المتأثّر مع تشويش عشوائي        |

بما أن الأجزاء مستقلّة، فإن فشلًا يكلّفك دومًا واحدًا منها فقط، وإعادة استدعاء `synthesize` لذلك الجزء آمنة دائمًا.

## الخطوات التالية

بعض الامتدادات الطبيعية من هنا:

* خزّن الصوت مؤقتًا حسب مُلخِّص هاش للنص والصوت والنموذج، بحيث لا يُعاد توليد الفقرات غير المتغيّرة أبدًا.
* استبدل الصوت بصوتٍ مستنسخ عبر [استنساخ الصوت](/guides/media/voice-cloning) ليستخدم السرد صوتك.
* ولّد النص المصدر بدلًا من استخراجه، باستخدام [إجابات موثّقة بالمصادر عبر البحث في الويب](/guides/tools/cited-web-answers).
* أضف مقدمة أو صوتًا خلفيًا عبر [الموسيقى والمؤثرات الصوتية](/guides/media/music-and-sound-effects).

<CardGroup cols={2}>
  <Card title="تحويل النص إلى كلام" icon="volume-2" href="/guides/media/text-to-speech">
    مرجع لنقطة نهاية الكلام ومعاملاتها.
  </Card>

  <Card title="استنساخ الصوت" icon="user" href="/guides/media/voice-cloning">
    اسرد بصوت مخصّص بدلًا من صوت جاهز.
  </Card>

  <Card title="إجابات موثّقة بالمصادر عبر البحث في الويب" icon="search" href="/guides/tools/cited-web-answers">
    ولِّد النص الذي ستسرده هذه الأداة.
  </Card>

  <Card title="تحويل الكلام إلى نص" icon="microphone" href="/guides/media/speech-to-text">
    فرِّغ الصوت للتحقق من سرد كامل من طرف إلى طرف.
  </Card>
</CardGroup>
