test
This commit is contained in:
158
src/app.ts
Normal file
158
src/app.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
const MINIMAX_API_KEY = process.env.MINIMAX_API_KEY;
|
||||
if (!MINIMAX_API_KEY) {
|
||||
throw new Error("MINIMAX_API_KEY environment variable is required");
|
||||
}
|
||||
|
||||
interface VoiceSetting {
|
||||
voice_id: string;
|
||||
speed: number;
|
||||
vol: number;
|
||||
pitch: number;
|
||||
emotion?: string;
|
||||
}
|
||||
|
||||
interface AudioSetting {
|
||||
sample_rate: number;
|
||||
bitrate: number;
|
||||
format: "mp3" | "wav" | "ogg" | "aac";
|
||||
channel: number;
|
||||
}
|
||||
|
||||
interface PronunciationDictEntry {
|
||||
text: string;
|
||||
pronunciation: string;
|
||||
}
|
||||
|
||||
interface MiniMaxTTSRequest {
|
||||
model: string;
|
||||
text: string;
|
||||
stream?: boolean;
|
||||
voice_setting: VoiceSetting;
|
||||
audio_setting: AudioSetting;
|
||||
pronunciation_dict?: {
|
||||
tone: PronunciationDictEntry[];
|
||||
};
|
||||
subtitle_enable?: boolean;
|
||||
}
|
||||
|
||||
interface MiniMaxTTSResponse {
|
||||
code: number;
|
||||
desc: string;
|
||||
data?: {
|
||||
audio?: string;
|
||||
audio_file?: string;
|
||||
subtitle_info?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const BASE_URL = "https://api.minimaxi.com/v1/t2a_v2";
|
||||
|
||||
export async function textToSpeech(
|
||||
request: MiniMaxTTSRequest
|
||||
): Promise<Buffer> {
|
||||
const response = await fetch(BASE_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${MINIMAX_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`MiniMax API error: ${response.status} ${response.statusText} - ${body}`);
|
||||
}
|
||||
|
||||
const result = (await response.json()) as MiniMaxTTSResponse;
|
||||
|
||||
if (result.code && result.code !== 0) {
|
||||
throw new Error(`MiniMax API error: ${result.code} - ${result.desc}`);
|
||||
}
|
||||
|
||||
if (!result.data?.audio && !result.data?.audio_file) {
|
||||
throw new Error("No audio data returned");
|
||||
}
|
||||
|
||||
const audioHex = result.data.audio || result.data.audio_file!;
|
||||
const audioBuffer = Buffer.from(audioHex, "hex");
|
||||
return audioBuffer;
|
||||
}
|
||||
|
||||
export async function textToSpeechStream(
|
||||
request: MiniMaxTTSRequest,
|
||||
onChunk: (chunk: Buffer) => void
|
||||
): Promise<void> {
|
||||
const response = await fetch(BASE_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${MINIMAX_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ ...request, stream: true }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`MiniMax API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("No response body");
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
onChunk(Buffer.from(value));
|
||||
}
|
||||
}
|
||||
|
||||
export const VOICE_IDS = {
|
||||
male_qn_qingse: "male-qn-qingse",
|
||||
female_tianmei: "female-tianmei",
|
||||
male_baixian: "male-baixian",
|
||||
female_aicheng: "female-aicheng",
|
||||
male_yunyang: "male-yunyang",
|
||||
female_xiaomo: "female-xiaomo",
|
||||
} as const;
|
||||
|
||||
export type VoiceId = (typeof VOICE_IDS)[keyof typeof VOICE_IDS];
|
||||
|
||||
export const EMOTIONS = {
|
||||
neutral: "neutral",
|
||||
happy: "happy",
|
||||
sad: "sad",
|
||||
angry: "angry",
|
||||
fearful: "fearful",
|
||||
disgust: "disgust",
|
||||
surprised: "surprised",
|
||||
} as const;
|
||||
|
||||
export type Emotion = (typeof EMOTIONS)[keyof typeof EMOTIONS];
|
||||
|
||||
// Usage example:
|
||||
// async function example() {
|
||||
// const audio = await textToSpeech({
|
||||
// model: "speech-2.8-hd",
|
||||
// text: "今天是不是很开心呀(laughs),当然了!",
|
||||
// stream: false,
|
||||
// voice_setting: {
|
||||
// voice_id: VOICE_IDS.male_qn_qingse,
|
||||
// speed: 1,
|
||||
// vol: 1,
|
||||
// pitch: 0,
|
||||
// emotion: EMOTIONS.happy,
|
||||
// },
|
||||
// audio_setting: {
|
||||
// sample_rate: 32000,
|
||||
// bitrate: 128000,
|
||||
// format: "mp3",
|
||||
// channel: 1,
|
||||
// },
|
||||
// });
|
||||
//
|
||||
// // Save audio to file (Node.js)
|
||||
// await import("fs/promises").then(fs => fs.writeFile("output.mp3", audio));
|
||||
// }
|
||||
Reference in New Issue
Block a user