@@ -24,8 +24,10 @@ import { downloadFile } from "bailian-cli-runtime";
2424import { runConcurrent , downloadParallel , getConcurrency } from "bailian-cli-runtime" ;
2525import { emitResult , emitBare } from "bailian-cli-runtime" ;
2626import { VOICE_TTS_PAGE } from "bailian-cli-runtime" ;
27+ import { rethrowWithSpeechSynthesizeHint } from "./synthesize-hint.ts" ;
2728
2829const COSYVOICE_CLONE_DESIGN_DOC = `${ DOCS_HOSTS . cn } /cosyvoice-clone-design-api` ;
30+ const QWEN_AUDIO_TTS_VOICE_DOC = `${ DOCS_HOSTS . cn } /qwen-audio-tts-voice-list` ;
2931
3032interface VoiceEntry {
3133 voice : string ;
@@ -34,6 +36,32 @@ interface VoiceEntry {
3436 lang : string ;
3537}
3638
39+ // qwen-audio-3.0-tts-plus system voices (official docs list only these two)
40+ const QWEN_AUDIO_30_TTS_PLUS_VOICES : VoiceEntry [ ] = [
41+ { voice : "longanlingxin" , name : "龙安灵心" , desc : "知心温暖音" , lang : "中文/英文" } ,
42+ { voice : "longanlufeng" , name : "龙安鲁风" , desc : "明亮开朗音" , lang : "中文/英文" } ,
43+ ] ;
44+
45+ // qwen-audio-3.0-tts-flash system voices
46+ const QWEN_AUDIO_30_TTS_FLASH_VOICES : VoiceEntry [ ] = [
47+ // Social companion (premium Chinese)
48+ { voice : "longanfengyue" , name : "龙安风悦" , desc : "自然亲切音" , lang : "中文/英文" } ,
49+ { voice : "longanyuanfei" , name : "龙安元妃" , desc : "高傲妃子音" , lang : "中文/英文" } ,
50+ { voice : "longanlingxi" , name : "龙安灵希" , desc : "可爱甜美音" , lang : "中文/英文" } ,
51+ { voice : "longanxiaoxin" , name : "龙安小昕" , desc : "亲切活泼音" , lang : "中文/英文" } ,
52+ { voice : "longanhuan_v3.6" , name : "龙安欢" , desc : "欢脱元气女" , lang : "中文/英文" } ,
53+ // Kids / toys (premium children)
54+ { voice : "longjielidou_v3.6" , name : "龙杰力豆" , desc : "天真男童" , lang : "中文/英文" } ,
55+ { voice : "longpaopao_v3.6" , name : "龙泡泡" , desc : "软糯可爱音" , lang : "中文/英文" } ,
56+ // Character / game (premium Chinese)
57+ { voice : "longhuohuo_v3.6" , name : "龙火火" , desc : "顽皮少年音" , lang : "中文/英文" } ,
58+ { voice : "longchuanshu_v3.6" , name : "龙川叔" , desc : "川普大叔音" , lang : "中文/英文" } ,
59+ // Social companion / assistant (premium English)
60+ { voice : "loongmary" , name : "loongmary" , desc : "温暖英音" , lang : "英文" } ,
61+ { voice : "loongeva_v3.6" , name : "loongeva" , desc : "高智美音" , lang : "英文" } ,
62+ { voice : "loongjohn" , name : "loongJohn" , desc : "沉稳亲切美音" , lang : "英文" } ,
63+ ] ;
64+
3765// cosyvoice-v3-flash system voices
3866const COSYVOICE_V3_FLASH_VOICES : VoiceEntry [ ] = [
3967 // 社交陪伴
@@ -111,18 +139,27 @@ const COSYVOICE_V3_FLASH_VOICES: VoiceEntry[] = [
111139] ;
112140
113141const MODEL_VOICES : Record < string , VoiceEntry [ ] > = {
142+ "qwen-audio-3.0-tts-plus" : QWEN_AUDIO_30_TTS_PLUS_VOICES ,
143+ "qwen-audio-3.0-tts-flash" : QWEN_AUDIO_30_TTS_FLASH_VOICES ,
114144 "cosyvoice-v3-flash" : COSYVOICE_V3_FLASH_VOICES ,
115145 "cosyvoice-v3-plus" : COSYVOICE_V3_FLASH_VOICES ,
116146 "cosyvoice-v3.5-flash" : [ ] ,
117147 "cosyvoice-v3.5-plus" : [ ] ,
118148 "cosyvoice-v2" : [ ] ,
119149} ;
120150
151+ /** Official voice-list docs URL for the model family. */
152+ function voiceDocsUrl ( model : string ) : string {
153+ if ( model . startsWith ( "qwen-audio-" ) ) return QWEN_AUDIO_TTS_VOICE_DOC ;
154+ return VOICE_TTS_PAGE ;
155+ }
156+
121157function printVoiceList ( model : string ) : void {
122158 const voices = MODEL_VOICES [ model ] ;
159+ const docsUrl = voiceDocsUrl ( model ) ;
123160 if ( ! voices ) {
124161 process . stdout . write ( `No built-in voice list available for model: ${ model } \n` ) ;
125- process . stdout . write ( `Browse voices in the console : ${ VOICE_TTS_PAGE } \n` ) ;
162+ process . stdout . write ( `See official voice list : ${ docsUrl } \n` ) ;
126163 return ;
127164 }
128165 if ( voices . length === 0 ) {
@@ -138,11 +175,13 @@ function printVoiceList(model: string): void {
138175 `${ col ( "VOICE ID" , 26 ) } ${ col ( "NAME" , 10 ) } ${ col ( "DESCRIPTION" , 16 ) } LANGUAGE\n` ,
139176 ) ;
140177 process . stdout . write ( `${ "-" . repeat ( 26 ) } ${ "-" . repeat ( 10 ) } ${ "-" . repeat ( 16 ) } ${ "-" . repeat ( 12 ) } \n` ) ;
141- for ( const v of voices ) {
142- process . stdout . write ( `${ col ( v . voice , 26 ) } ${ col ( v . name , 10 ) } ${ col ( v . desc , 16 ) } ${ v . lang } \n` ) ;
178+ for ( const entry of voices ) {
179+ process . stdout . write (
180+ `${ col ( entry . voice , 26 ) } ${ col ( entry . name , 10 ) } ${ col ( entry . desc , 16 ) } ${ entry . lang } \n` ,
181+ ) ;
143182 }
144183 process . stdout . write ( `\nTotal: ${ voices . length } voices\n` ) ;
145- process . stdout . write ( `Preview and browse more voices in the console : \n${ VOICE_TTS_PAGE } \n` ) ;
184+ process . stdout . write ( `Preview and browse more voices: \n${ docsUrl } \n` ) ;
146185}
147186
148187const SYNTHESIZE_FLAGS = {
@@ -177,9 +216,9 @@ const SYNTHESIZE_FLAGS = {
177216 valueHint : "<voice>" ,
178217 description : {
179218 "en-US" :
180- "Voice ID. Use --list-voices to see built-in voices for cosyvoice-v3-flash; for v3.5-flash provide a clone/design voice ID" ,
219+ "Voice ID. Use --list-voices for the selected model (e.g. qwen-audio-3.0-tts-plus/flash, cosyvoice-v3-flash) ; for v3.5-flash provide a clone/design voice ID" ,
181220 "zh-CN" :
182- "音色 ID。使用 --list-voices 查看 cosyvoice-v3-flash 的内置音色 ;使用 v3.5-flash 时需提供复刻/设计音色 ID" ,
221+ "音色 ID。使用 --list-voices 查看所选模型的内置音色(如 qwen-audio-3.0-tts-plus/flash、 cosyvoice-v3-flash) ;使用 v3.5-flash 时需提供复刻/设计音色 ID" ,
183222 } ,
184223 } ,
185224 listVoices : {
@@ -290,10 +329,11 @@ export default defineCommand({
290329 usageArgs : "--text <text> [flags]" ,
291330 flags : SYNTHESIZE_FLAGS ,
292331 exampleArgs : [
332+ "--list-voices --model qwen-audio-3.0-tts-plus" ,
293333 "--list-voices --model cosyvoice-v3-flash" ,
294334 {
295- "en-US" : '--text "Hello, I am Qwen" --voice <voice_id> ' ,
296- "zh-CN" : '--text "你好,我是通义千问" --voice <voice_id> ' ,
335+ "en-US" : '--text "Hello, I am Qwen" --model qwen-audio-3.0-tts-plus -- voice longanlingxin ' ,
336+ "zh-CN" : '--text "你好,我是通义千问" --model qwen-audio-3.0-tts-plus -- voice longanlingxin ' ,
297337 } ,
298338 {
299339 "en-US" : '--text "Hello world" --voice <voice_id> --language en' ,
@@ -398,10 +438,17 @@ export default defineCommand({
398438 process . stderr . write ( `[Model: ${ model } ] [Voice: ${ voice } ]\n` ) ;
399439 }
400440
401- if ( useStream ) {
402- await handleStreamMode ( ctx . client , settings , body , flags , format ) ;
403- } else {
404- await handleNonStreamMode ( ctx . client , settings , body , flags , format ) ;
441+ try {
442+ if ( useStream ) {
443+ await handleStreamMode ( ctx . client , settings , body , flags , format ) ;
444+ } else {
445+ await handleNonStreamMode ( ctx . client , settings , body , flags , format ) ;
446+ }
447+ } catch ( error ) {
448+ rethrowWithSpeechSynthesizeHint ( error , {
449+ binName : ctx . identity . binName ,
450+ model,
451+ } ) ;
405452 }
406453 } ,
407454} ) ;
0 commit comments