|
|
@@ -22,37 +22,93 @@ function logToFile(msg: string) {
|
|
|
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${msg}\n`);
|
|
|
}
|
|
|
|
|
|
-// 可用音色列表(使用阿里云官方音色)
|
|
|
+// 可用音色列表(CosyVoice v3-flash - 67个音色)
|
|
|
+// 数据来源:阿里云百炼官方文档 2026-05
|
|
|
export const VOICES: Voice[] = [
|
|
|
- { id: 'cherry', name: '芊悦', gender: 'female', description: '阳光积极、亲切自然' },
|
|
|
- { id: 'serena', name: '苏瑶', gender: 'female', description: '温柔女声' },
|
|
|
- { id: 'ethan', name: '晨煦', gender: 'male', description: '阳光温暖、活力男声' },
|
|
|
- { id: 'chelsie', name: '千雪', gender: 'female', description: '二次元虚拟女友' },
|
|
|
- { id: 'momo', name: '茉兔', gender: 'female', description: '撒娇搞怪' },
|
|
|
- { id: 'vivian', name: '十三', gender: 'female', description: '可爱小暴躁' },
|
|
|
- { id: 'moon', name: '月白', gender: 'male', description: '率性帅气' },
|
|
|
- { id: 'maia', name: '四月', gender: 'female', description: '知性温柔' },
|
|
|
- { id: 'kai', name: '凯', gender: 'male', description: '舒缓放松' },
|
|
|
- { id: 'nofish', name: '不吃鱼', gender: 'male', description: '不会翘舌音' },
|
|
|
+ // 标杆音色
|
|
|
+ { id: 'longanyang', name: '龙安洋', gender: 'male', description: '阳光大男孩 20~30岁' },
|
|
|
+ { id: 'longanhuan', name: '龙安欢', gender: 'female', description: '欢脱元气女 20~30岁' },
|
|
|
+ { id: 'longhuhu_v3', name: '龙呼呼', gender: 'female', description: '天真烂漫女童 6~10岁' },
|
|
|
+ // 有声书 - 男声
|
|
|
+ { id: 'longsanshu_v3', name: '龙三叔', gender: 'male', description: '沉稳质感男 25~45岁' },
|
|
|
+ { id: 'longxiu_v3', name: '龙修', gender: 'male', description: '博才说书男 25~35岁' },
|
|
|
+ { id: 'longnan_v3', name: '龙楠', gender: 'male', description: '睿智青年男 25~30岁' },
|
|
|
+ { id: 'longyichen_v3', name: '龙逸尘', gender: 'male', description: '洒脱活力男 20~30岁' },
|
|
|
+ { id: 'longlaobo_v3', name: '龙老伯', gender: 'male', description: '沧桑岁月爷 60岁以上' },
|
|
|
+ // 有声书 - 女声
|
|
|
+ { id: 'longmiao_v3', name: '龙妙', gender: 'female', description: '抑扬顿挫女 25~30岁' },
|
|
|
+ { id: 'longyuan_v3', name: '龙媛', gender: 'female', description: '温暖治愈女 35~40岁' },
|
|
|
+ { id: 'longyue_v3', name: '龙悦', gender: 'female', description: '温暖磁性女 30~35岁' },
|
|
|
+ { id: 'longwanjun_v3', name: '龙婉君', gender: 'female', description: '细腻柔声女 20~30岁' },
|
|
|
+ { id: 'longlaoyi_v3', name: '龙老姨', gender: 'female', description: '烟火从容阿姨 60岁以上' },
|
|
|
+ // 客服/电话
|
|
|
+ { id: 'longyingxun_v3', name: '龙应询', gender: 'male', description: '年轻青涩男 20~25岁' },
|
|
|
+ { id: 'longyingjing_v3', name: '龙应静', gender: 'female', description: '低调冷静女 25~35岁' },
|
|
|
+ { id: 'longyingling_v3', name: '龙应聆', gender: 'female', description: '温和共情女 25~30岁' },
|
|
|
+ { id: 'longyingtao_v3', name: '龙应桃', gender: 'female', description: '温柔淡定女 25~30岁' },
|
|
|
+ { id: 'longyingmu_v3', name: '龙应沐', gender: 'female', description: '优雅知性女 25~30岁' },
|
|
|
+ { id: 'longyingxiao_v3', name: '龙应笑', gender: 'female', description: '清甜推销女 20~25岁' },
|
|
|
+ // 语音助手
|
|
|
+ { id: 'longxiaochun_v3', name: '龙小淳', gender: 'female', description: '知性积极女 25~30岁' },
|
|
|
+ { id: 'longxiaoxia_v3', name: '龙小夏', gender: 'female', description: '沉稳权威女 25~30岁' },
|
|
|
+ { id: 'longanyun_v3', name: '龙安昀', gender: 'male', description: '居家暖男 30~35岁' },
|
|
|
+ { id: 'longanwen_v3', name: '龙安温', gender: 'female', description: '优雅知性女 25~35岁' },
|
|
|
+ { id: 'longanli_v3', name: '龙安莉', gender: 'female', description: '利落从容女 25~35岁' },
|
|
|
+ { id: 'longanlang_v3', name: '龙安朗', gender: 'male', description: '清爽利落男 20~25岁' },
|
|
|
+ { id: 'longyumi_v3', name: 'YUMI', gender: 'female', description: '正经青年女 20~25岁' },
|
|
|
+ // 社交陪伴
|
|
|
+ { id: 'longanqin_v3', name: '龙安亲', gender: 'female', description: '亲和活泼女 20~25岁' },
|
|
|
+ { id: 'longanya_v3', name: '龙安雅', gender: 'female', description: '高雅气质女 25~35岁' },
|
|
|
+ { id: 'longanzhi_v3', name: '龙安智', gender: 'male', description: '睿智轻熟男 25~35岁' },
|
|
|
+ { id: 'longanling_v3', name: '龙安灵', gender: 'female', description: '思维灵动女 20~30岁' },
|
|
|
+ { id: 'longantai_v3', name: '龙安台', gender: 'female', description: '嗲甜台湾女 20~25岁' },
|
|
|
+ { id: 'longhua_v3', name: '龙华', gender: 'female', description: '元气甜美女 20~25岁' },
|
|
|
+ { id: 'longcheng_v3', name: '龙橙', gender: 'male', description: '智慧青年男 20~25岁' },
|
|
|
+ { id: 'longze_v3', name: '龙泽', gender: 'male', description: '温暖元气男 25~30岁' },
|
|
|
+ { id: 'longzhe_v3', name: '龙哲', gender: 'male', description: '呆板大暖男 25~30岁' },
|
|
|
+ { id: 'longyan_v3', name: '龙颜', gender: 'female', description: '温暖春风女 30~35岁' },
|
|
|
+ { id: 'longxing_v3', name: '龙星', gender: 'female', description: '温婉邻家女 20~25岁' },
|
|
|
+ { id: 'longtian_v3', name: '龙天', gender: 'male', description: '磁性理智男 30~35岁' },
|
|
|
+ { id: 'longwan_v3', name: '龙婉', gender: 'female', description: '细腻柔声女 20~30岁' },
|
|
|
+ { id: 'longqiang_v3', name: '龙嫱', gender: 'female', description: '浪漫风情女 30~35岁' },
|
|
|
+ { id: 'longfeifei_v3', name: '龙菲菲', gender: 'female', description: '甜美娇气女 20~25岁' },
|
|
|
+ { id: 'longhao_v3', name: '龙浩', gender: 'male', description: '多情忧郁男 30~35岁' },
|
|
|
+ { id: 'longanrou_v3', name: '龙安柔', gender: 'female', description: '温柔闺蜜女 20~35岁' },
|
|
|
+ { id: 'longhan_v3', name: '龙寒', gender: 'male', description: '温暖痴情男 30~35岁' },
|
|
|
+ // 童声
|
|
|
+ { id: 'longpaopao_v3', name: '龙泡泡', gender: 'female', description: '飞天泡泡音 6~15岁' },
|
|
|
+ { id: 'longjielidou_v3', name: '龙杰力豆', gender: 'male', description: '阳光顽皮男 10岁' },
|
|
|
+ { id: 'longxian_v3', name: '龙仙', gender: 'female', description: '豪放可爱女 12岁' },
|
|
|
+ { id: 'longling_v3', name: '龙铃', gender: 'female', description: '稚气呆板女 10岁' },
|
|
|
+ { id: 'longshanshan_v3', name: '龙闪闪', gender: 'female', description: '戏剧化童声 6~15岁' },
|
|
|
+ { id: 'longniuniu_v3', name: '龙牛牛', gender: 'male', description: '阳光男童声 6~15岁' },
|
|
|
+ // 方言
|
|
|
+ { id: 'longjiaxin_v3', name: '龙嘉欣', gender: 'female', description: '优雅粤语女 30~35岁' },
|
|
|
+ { id: 'longjiayi_v3', name: '龙嘉怡', gender: 'female', description: '知性粤语女 25~30岁' },
|
|
|
+ { id: 'longanyue_v3', name: '龙安粤', gender: 'male', description: '欢脱粤语男 25~35岁' },
|
|
|
+ { id: 'longlaotie_v3', name: '龙老铁', gender: 'male', description: '东北直率男 25~30岁' },
|
|
|
+ { id: 'longshange_v3', name: '龙陕哥', gender: 'male', description: '陕北原味男 25~35岁' },
|
|
|
+ // 新闻/直播
|
|
|
+ { id: 'longshuo_v3', name: '龙硕', gender: 'male', description: '博才干练男 25~30岁' },
|
|
|
+ { id: 'longshu_v3', name: '龙书', gender: 'male', description: '沉稳青年男 20~25岁' },
|
|
|
+ { id: 'loongbella_v3', name: 'Bella3.0',gender: 'female', description: '精准干练女 25~30岁' },
|
|
|
+ { id: 'longanran_v3', name: '龙安燃', gender: 'female', description: '活泼质感女 30~40岁' },
|
|
|
+ { id: 'longanxuan_v3', name: '龙安宣', gender: 'female', description: '经典直播女 30~40岁' },
|
|
|
+ // 特色
|
|
|
+ { id: 'longjiqi_v3', name: '龙机器', gender: 'male', description: '呆萌机器人 20~30岁' },
|
|
|
+ { id: 'longhouge_v3', name: '龙猴哥', gender: 'male', description: '经典猴哥 20~25岁' },
|
|
|
+ { id: 'longdaiyu_v3', name: '龙黛玉', gender: 'female', description: '娇率才女音 15~25岁' },
|
|
|
+ { id: 'longfei_v3', name: '龙飞', gender: 'male', description: '热血磁性男 30~35岁' },
|
|
|
+ // 外语
|
|
|
+ { id: 'loongkyong_v3', name: '韩语女', gender: 'female', description: '韩语女声' },
|
|
|
+ { id: 'loongriko_v3', name: 'Riko霓虹', gender: 'female', description: '二次元日语女' },
|
|
|
+ { id: 'loongabby_v3', name: '美语女', gender: 'female', description: '美式英文女' },
|
|
|
+ { id: 'loongandy_v3', name: '美语男', gender: 'male', description: '美式英文男' },
|
|
|
];
|
|
|
|
|
|
-// 前端音色 ID 到阿里云音色的映射
|
|
|
-const VOICE_MAPPING: Record<string, string> = {
|
|
|
- cherry: 'Cherry',
|
|
|
- serena: 'Serena',
|
|
|
- ethan: 'Ethan',
|
|
|
- chelsie: 'Chelsie',
|
|
|
- momo: 'Momo',
|
|
|
- vivian: 'Vivian',
|
|
|
- moon: 'Moon',
|
|
|
- maia: 'Maia',
|
|
|
- kai: 'Kai',
|
|
|
- nofish: 'Nofish',
|
|
|
-};
|
|
|
-
|
|
|
-// 获取阿里云音色名称
|
|
|
+// CosyVoice v3-flash 音色ID直接用作API参数名,无需映射
|
|
|
export function getAliyunVoice(voiceId: string): string {
|
|
|
- return VOICE_MAPPING[voiceId] || 'Cherry';
|
|
|
+ return voiceId;
|
|
|
}
|
|
|
|
|
|
// 获取或创建默认"我的音频"书籍
|
|
|
@@ -887,7 +943,7 @@ function buildSimpleLrc(text: string, duration: number): string {
|
|
|
}
|
|
|
if (sentences.length <= 1 && text.length > 20) {
|
|
|
// 英文按句号切分后再按固定长度兜底
|
|
|
- const englishSentences = splitByEnglishSentence(text, 50);
|
|
|
+ const englishSentences = text.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 0);
|
|
|
if (englishSentences.length > 1) {
|
|
|
sentences = englishSentences;
|
|
|
} else {
|