| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- import type {
- TTSSynthesizeRequest,
- TTSResult,
- VoiceInfo,
- WordTimestamp,
- } from "@pipeline/shared";
- import type { TTSProvider } from "../types.js";
- import { registerProvider } from "../registry.js";
- import { measureAudioDuration } from "../duration.js";
- import { fetchWithRetry } from "../retry.js";
- class OpenAITTSProvider implements TTSProvider {
- readonly name = "openai-tts";
- async synthesize(request: TTSSynthesizeRequest): Promise<TTSResult> {
- const apiKey = process.env.OPENAI_TTS_API_KEY;
- if (!apiKey) throw new Error("OPENAI_TTS_API_KEY not set");
- const baseURL =
- process.env.OPENAI_TTS_BASE_URL ||
- "https://new-api.corp.shuidi.tech/v1";
- const model = process.env.OPENAI_TTS_MODEL || "seed-tts-1.1";
- const response = await fetchWithRetry(`${baseURL}/audio/speech`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${apiKey}`,
- },
- body: JSON.stringify({
- input: request.text,
- model,
- response_format: request.format || "mp3",
- speed: request.speed ?? 1.0,
- voice: request.voiceId || "zh_female_linjianvhai_uranus_bigtts",
- }),
- });
- if (!response.ok) {
- const body = await response.text();
- throw new Error(`OpenAI TTS API error: ${response.status} ${body}`);
- }
- const audioBuffer = Buffer.from(await response.arrayBuffer());
- const format = request.format || "mp3";
- const outputPath = `${request.outputDir}/${request.filename}.${format}`;
- const { writeFile, mkdir } = await import("node:fs/promises");
- await mkdir(request.outputDir, { recursive: true });
- await writeFile(outputPath, audioBuffer);
- const durationSeconds = await measureAudioDuration(outputPath);
- return {
- audioFilePath: outputPath,
- durationSeconds,
- wordTimestamps: estimateWordTimestamps(request.text, durationSeconds),
- format,
- };
- }
- async listVoices(): Promise<VoiceInfo[]> {
- // Voice IDs are configured via Volcengine docs:
- // https://www.volcengine.com/docs/6561/1257544
- return [];
- }
- async align(audio: Buffer, text: string): Promise<WordTimestamp[]> {
- return estimateWordTimestamps(text, (audio.length * 8) / 128000);
- }
- }
- function estimateWordTimestamps(
- text: string,
- totalDuration: number
- ): WordTimestamp[] {
- const isChinese = /[一-鿿]/.test(text);
- const words = isChinese
- ? text.split("").filter((c) => c.trim())
- : text.split(/\s+/).filter((w) => w);
- const durationPerWord = totalDuration / Math.max(words.length, 1);
- return words.map((word, i) => ({
- word,
- startSeconds: i * durationPerWord,
- endSeconds: (i + 1) * durationPerWord,
- }));
- }
- registerProvider("openai-tts", () => new OpenAITTSProvider());
|