توليد ملف WAV قابل للتشغيل
اكتشف صوتًا، وولد PCM16 باستخدام SDK 0.18.0، وغلّفه في ملف WAV متحقق منه.
تحول هذه الوصفة إدخالًا نصيًا يحتوي بعد إزالة الفراغات على حرف Unicode أو رقم واحد على الأقل إلى ملف كامل لمشغل الوسائط. تكتشف صوتًا أثناء التشغيل، وتتوقف بأمان عندما لا يعود أي صوت، وتنتظر الصوت النهائي، وتكتب بيانات PCM الوصفية الصحيحة، وتغلق العميل في كل المسارات.
متى تستخدم هذه الوصفة
استخدم المسار المخزن عندما يحتاج التطبيق إلى ملف WAV كامل قبل نشر النتيجة أو تخزينها أو تشغيلها. إذا وجب بدء التشغيل أو المعالجة قبل انتهاء التوليف، فاستخدم دالة البث الموضحة أدناه وأبقِ حدود المقطع النهائي والحاوية والمهلة والخطأ والتنظيف نفسها.
تغطي هذه الوصفة TTS عبر Socket.IO باستخدام حزمتَي JavaScript وPython المنشورتين. ولا تحدد عقد الخرج لنقل مختلف.
قبل أن تبدأ
| المتطلب | عقد 0.18.0 |
|---|---|
| SDK | @humain-voice/sdk@0.18.0 أوhumain-voice==0.18.0 في وقت تشغيل موثوق على الخادم |
| الاتصال | القيم المخصصة API_URL وAPI_KEY؛ ويستخدم SDK /socket.io افتراضيًا |
| النص | يحتوي بعد إزالة الفراغات على حرف Unicode أو رقم واحد على الأقل؛ ولا يقبل الفراغات فقط أو علامات الترقيم فقط |
| إدخال الصوت | واحد بالضبط من voice_id أو مجموعة voice_references غير فارغة |
| الخرج | وجهة قابلة للكتابة لملف WAV النهائي |
export API_URL="https://api.voice.humain.com"
export API_KEY="YOUR_API_KEY"يكتشف المسار المختبر voice_id؛ ولا يفترض توفر صوت محدد أو أي صوت. أبقِ مفتاح
API في وقت التشغيل الموثوق؛ ويستخدم SDK المسار /socket.io افتراضيًا.
إذا استخدم التطبيق voice_references بدلًا من ذلك، فأرسل مرجعًا واحدًا يكون
صوته RIFF/WAVE بترميز base64 القياسي ويحتوي بيانات PCM16 أحادية غير فارغة.
1. اكتشف صوتًا
استدعِ listVoices() / list_voices() قبل التوليف عندما لا يكون التطبيق قد
استلم مرجع صوت مدعومًا.
| وقت التشغيل | القيمة الافتراضية المنشورة | هذه الوصفة |
|---|---|---|
| JavaScript | القيمة الافتراضية لـlistVoices() خمس ثوان | تمرر timeoutSeconds: 5 صراحة |
| Python | لا مهلة لـlist_voices() ما لم تمرر | تمرر timeout_seconds=5.0 |
تحتوي النتيجة سبع هويات متعددة اللغات بالشكل { id, label, profile } عند
توفر كل النسخ المهيأة. تعامل مع القائمة كبيانات وقت تشغيل:
- إذا كانت القائمة فارغة، فأوقف مسار
voice_idهذا قبل فهرستها. - إذا فشل الطلب، فاحتفظ بحالة خطئه المنظمة ونظف الموارد.
- إذا عاد صوت، فمرر
idالدقيق؛ ولا تشتق معرّفًا منlabel.
يتيح تمرير معرّف هوية للمنصة اختيار نسختها الفعلية من النص. في هويات العربية/الإنجليزية الحالية، يختار أي حرف من محارف الكتابة العربية النسخة العربية؛ وإلا تُختار الإنجليزية. تبقى معرّفات النسخ الفعلية داخلية وتُرفض.
القائمة الفارغة نتيجة تشغيلية وليست وعدًا حول توفر الأصوات مستقبلًا. لا تخترع معرّف صوت احتياطيًا.
2. شغّل المسار الناجح المختبر
تطلب البرامج الأصوات، وترفض القائمة الفارغة، وتختار TtsModel.Nebula، وتولد
النص Hello from HUMAIN Voice، وتشتق معدل عينات النموذج، وتكتب speech.wav،
وتغلق العميل.
import { writeFile } from 'node:fs/promises';
import {
TTSClient,
TtsModel,
getSampleRate,
} from '@humain-voice/sdk';
function requiredEnv(name: 'API_KEY' | 'API_PATH' | 'API_URL'): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
function pcm16ToWav(pcm: Uint8Array, sampleRate: number): Buffer {
const header = Buffer.alloc(44);
header.write('RIFF', 0);
header.writeUInt32LE(36 + pcm.byteLength, 4);
header.write('WAVE', 8);
header.write('fmt ', 12);
header.writeUInt32LE(16, 16);
header.writeUInt16LE(1, 20); // Linear PCM.
header.writeUInt16LE(1, 22); // Mono.
header.writeUInt32LE(sampleRate, 24);
header.writeUInt32LE(sampleRate * 2, 28);
header.writeUInt16LE(2, 32);
header.writeUInt16LE(16, 34);
header.write('data', 36);
header.writeUInt32LE(pcm.byteLength, 40);
return Buffer.concat([
header,
Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength),
]);
}
async function main(): Promise<void> {
const outputPath = process.argv[2] ?? 'speech.wav';
const client = new TTSClient({
api_url: requiredEnv('API_URL'),
api_path: requiredEnv('API_PATH'),
api_key: requiredEnv('API_KEY'),
});
try {
const voices = await client.listVoices({ timeoutSeconds: 5 });
const voice = voices.find((candidate) => candidate.profile) ?? voices[0];
if (!voice) throw new Error('No TTS voices are available');
if (voice.profile) {
console.log(
'profile:',
voice.label,
voice.profile.speaker.dialect,
voice.profile.languages,
);
}
const model = TtsModel.Nebula;
const pcm = await client.synthesize('Hello from HUMAIN Voice', {
voice_id: voice.id,
model,
// This is an inactivity timeout applied while awaiting each audio chunk.
timeoutSeconds: 30,
onError: (error) => console.error('server error:', error.code, error.message),
});
await writeFile(outputPath, pcm16ToWav(pcm, getSampleRate(model)));
} finally {
await client.close();
}
}
void main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
للتشغيل الناجح هذه النتائج الملحوظة:
- يعيد اكتشاف الأصوات عنصرًا واحدًا على الأقل لهذا الطلب.
- يستقبل التوليف المخزن مقطع الصوت النهائي من دون خطأ خادم مسجل أو انتهاء مهلة الخمول.
- يبدأ
speech.wavبترويسة RIFF/WAVE صالحة تتبعها كل بايتات PCM المعادة، ويمكن لمشغل يدعم WAV فتحه. - يغلق عميل JavaScript داخل
finally؛ وتخرج Python من سياق العميل غير المتزامن قبل كتابة الملف.
إذا لم يعد اكتشاف الأصوات أي عنصر، تفشل البرامج بوضوح ولا تنشئ ملفًا صامتًا مضللًا.
3. غلّف PCM الخام في WAV
قيمة SDK المعادة بيانات صوت وليست ملف وسائط جاهزًا:
| الطبقة | القيمة المستخدمة في الأمثلة |
|---|---|
| صوت SDK | PCM16 little-endian خام بإشارة، بتردد 24 kHz، وأحادي القناة |
| عرض العينة | 16 بت، أو بايتان |
| ترويسة WAV | ترويسة RIFF/WAVE من 44 بايت تحمل صيغة PCM وعدد القنوات ومعدل العينات ومعدل البايتات ومحاذاة الكتل وطول البيانات |
| جسم WAV | كل بايتات PCM المعادة بعد وصول التوليف إلى مقطعه النهائي |
يحصل البرنامجان على معدل العينات عبر getSampleRate(model) /
get_sample_rate(model) بدل معاملة ترويسة الحاوية كجزء من استجابة SDK.
يقبل مساعد JavaScript نوع Uint8Array المنشور مباشرة، ويحافظ على
byteOffset وbyteLength عند إنشاء Buffer في Node.js. تستخدم Python وحدة
wave القياسية لكتابة البيانات الوصفية نفسها.
ينطبق عقد PCM بتردد 24 kHz على TTS عبر Socket.IO وعملاء SDK هؤلاء. اقرأ عقد العملية الحالي قبل تغليف البايتات المعادة من نقل آخر.
4. اختر التوليف المخزن أو المتدفق
| الوضع | API | النتيجة والمسؤولية |
|---|---|---|
| مخزن | synthesize() | يعيد Uint8Array واحدًا في JavaScript أوbytes في Python بعد أن يجمع SDK الصوت حتى المقطع النهائي. بسيط للملفات المحدودة، لكنه يحتفظ بنتيجة PCM كاملة في الذاكرة. |
| متدفق | synthesizeStream() / synthesize_stream() | يولد استجابات تحمل id وaudio وis_last؛ عالج الصوت مبكرًا، وتتبع مجموع البايتات، وأنهِ حاوية صالحة فقط بعد الاستجابة النهائية. |
في الوضعين، يكون SDK قد أزال ترويسة إطار TTS عبر Socket.IO ذات 17 بايت. اكتب
بايتات audio لكل استجابة، لا الحمولة المؤطرة الأصلية. حد الشبكة أو المكرر
العشوائي ليس اكتمالًا؛ يحدد is_last=true استجابة الصوت النهائية.
يمكن للتوليف المخزن استقبال المقاطع أيضًا عبر onAudio / on_audio، لكن
الاستدعاء المخزن نفسه لا يكتمل إلا بعد جمع الصوت النهائي. إذا انتهى التدفق من
دون مقطع نهائي، فلا تنشر الملف الجزئي على أنه ملف كامل.
5. حد الخمول واحتفظ بحالة الخطأ
| وقت التشغيل | مهلة قائمة الأصوات | مهلة التوليف |
|---|---|---|
| JavaScript | افتراضيًا خمس ثوان | القيمة الافتراضية لـtimeoutSeconds هي 30 ثانية من الخمول |
| Python | لا قيمة افتراضية | لا قيمة افتراضية؛ مرر timeout_seconds دائمًا |
تستخدم الأمثلة صراحة خمس ثوان لاكتشاف الأصوات و30 ثانية للتوليف. قيمة التوليف مهلة خمول أثناء انتظار مقطع الصوت التالي، وليست مهلة عامة للمسار. أضف مهلة تطبيق مستقلة للاتصال والاكتشاف والتوليف وكتابة الملف والتنظيف.
ويفرض الخادم بصورة مستقلة مهلة كلية غير قابلة لإعادة الضبط قدرها 25 ثانية
ومراقب خمول قدره 60 ثانية. إذا سبقت المهلة الكلية الصوت النهائي، يصدر
TTS_DEADLINE_EXCEEDED القابل لإعادة المحاولة ويظل الصوت المستلم جزئيًا.
يعرض TTS سطحي خطأ مختلفين:
| السطح | المعلومات المحتفظ بها |
|---|---|
استدعاء onError / on_error | كائن ErrorResponse مسوّى؛ قد تحتفظ الحمولات المنظمة بـid وcode وretryable وtimestamp وretry_after_seconds وdata وreason وretry_scope وmessage. تصبح الحمولة القديمة غير الكائنية رسالة. |
| التوليف المرفوض | Error عام في JavaScript أوRuntimeError في Python يحمل الرسالة فقط |
سجل حقول الاستدعاء المنظمة قبل التنظيف. لا تستنتج قابلية إعادة المحاولة من تحليل رسالة الرفض العامة، ولا تدّع أن الإعادة آمنة عندما لا يوفر الاستدعاء حالة كافية.
6. نظف الموارد وانشر ذريًا
أغلق العميل حتى عندما تكون قائمة الأصوات فارغة أو يفشل التوليف أو لا يصل
المقطع النهائي أو تفشل كتابة الملف. يستدعي مثال JavaScript
client.close() داخل finally؛ ويستخدم مثال Python عبارة async with لتحرير
Socket.IO وموارد HTTP الداخلية.
يغلق التنظيف اتصال العميل، فيلغي طلبات التوليف النشطة التي يملكها ذلك الاتصال ويمنع أحداث الصوت والخطأ اللاحقة. ولا يغلق اتصالات الاستدلال المشتركة مع طلبات أخرى.
في مسار ملفات الإنتاج، اكتب إلى وجهة مؤقتة ولا تكشف الملف إلا بعد الصوت النهائي، وترويسة وجسم WAV مكتملين، وإغلاق ملف ناجح. يبقى التنظيف مطلوبًا إذا فشل نشر الملف.