|
|
@@ -1,5 +1,7 @@
|
|
|
import Router from '@koa/router';
|
|
|
import { Context } from 'koa';
|
|
|
+import path from 'path';
|
|
|
+import fs from 'fs';
|
|
|
import * as TtsService from './tts.service';
|
|
|
import { BadRequestError, NotFoundError } from '../../middleware/errorHandler';
|
|
|
import { optionalAuth } from '../../middleware/auth';
|
|
|
@@ -12,11 +14,20 @@ const router = new Router();
|
|
|
// 获取可用音色列表
|
|
|
router.get('/voices', async (ctx: Context) => {
|
|
|
const voices = TtsService.getVoices();
|
|
|
+ const baseUrl = `${ctx.protocol}://${ctx.host}`;
|
|
|
+
|
|
|
+ // 为每个音色注入本地预览URL
|
|
|
+ const result = voices.map(v => ({
|
|
|
+ ...v,
|
|
|
+ preview: fs.existsSync(path.join(process.cwd(), '..', 'voices', `${v.id}.mp3`))
|
|
|
+ ? `${baseUrl}/tts/voices/${v.id}.mp3`
|
|
|
+ : undefined,
|
|
|
+ }));
|
|
|
|
|
|
ctx.body = {
|
|
|
code: 0,
|
|
|
message: 'success',
|
|
|
- data: { voices },
|
|
|
+ data: { voices: result },
|
|
|
};
|
|
|
});
|
|
|
|
|
|
@@ -142,9 +153,9 @@ router.get('/status/:audioId', async (ctx: Context) => {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
-// 预览音色 - 生成短音频
|
|
|
+// 预览音色 - 优先使用本地样本文件,不存在时才调用API生成
|
|
|
router.post('/preview', async (ctx: Context) => {
|
|
|
- const { voiceId, voiceParams, ttsProvider } = ctx.request.body as {
|
|
|
+ const { voiceId } = ctx.request.body as {
|
|
|
voiceId: string;
|
|
|
voiceParams?: { speed?: number; pitch?: number; volume?: number };
|
|
|
ttsProvider?: 'aliyun' | 'minimax';
|
|
|
@@ -154,22 +165,36 @@ router.post('/preview', async (ctx: Context) => {
|
|
|
throw new BadRequestError('请选择音色');
|
|
|
}
|
|
|
|
|
|
- // 预览文本(固定短文本)
|
|
|
- const previewText = '你好,欢迎使用AI有声书';
|
|
|
-
|
|
|
- try {
|
|
|
- // 生成预览音频
|
|
|
- const result = await TtsService.generatePreview(voiceId, voiceParams, ttsProvider);
|
|
|
+ const baseUrl = `${ctx.protocol}://${ctx.host}`;
|
|
|
+ const localFile = `/tts/voices/${voiceId}.mp3`;
|
|
|
+ const filePath = path.join(process.cwd(), '..', 'voices', `${voiceId}.mp3`);
|
|
|
|
|
|
+ // 检查本地样本文件是否存在
|
|
|
+ if (fs.existsSync(filePath)) {
|
|
|
+ console.log(`🎵 [Preview] 使用本地样本: ${voiceId}`);
|
|
|
ctx.body = {
|
|
|
code: 0,
|
|
|
message: 'success',
|
|
|
data: {
|
|
|
- previewText,
|
|
|
+ previewText: `${voiceId} 音色试听`,
|
|
|
voiceId,
|
|
|
- audioUrl: result.audioUrl,
|
|
|
+ audioUrl: `${baseUrl}${localFile}`,
|
|
|
},
|
|
|
};
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 本地不存在,降级到 API 生成
|
|
|
+ console.log(`🔊 [Preview] 本地无样本,API生成: ${voiceId}`);
|
|
|
+ const previewText = '你好,欢迎使用AI有声书';
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await TtsService.generatePreview(voiceId, undefined, undefined);
|
|
|
+ ctx.body = {
|
|
|
+ code: 0,
|
|
|
+ message: 'success',
|
|
|
+ data: { previewText, voiceId, audioUrl: result.audioUrl },
|
|
|
+ };
|
|
|
} catch (error: any) {
|
|
|
ctx.body = {
|
|
|
code: 500,
|