const fs = require('fs') const path = require('path') const crypto = require('crypto') const envPath = path.join(process.env.USERPROFILE || 'C:\\Users\\Administrator', 'Toolbox_local_creds.env.local') if (fs.existsSync(envPath)) { const text = fs.readFileSync(envPath, 'utf8') for (const line of text.split(/\r?\n/)) { if (!line || /^\s*#/.test(line)) continue const i = line.indexOf('=') if (i < 0) continue const k = line.slice(0, i).trim(), v = line.slice(i + 1).trim().replace(/^["']|["']$/g, '') if (k && !(k in process.env)) process.env[k] = v } } process.chdir('d:/Trae_Files/TRAE-Toolbox') global.__builtin_fetch = globalThis.fetch const TTS_ROOT = path.join(process.cwd(), 'uploads', 'mp_rep_clock', 'tts_cache') if (!fs.existsSync(TTS_ROOT)) fs.mkdirSync(TTS_ROOT, { recursive: true }) const VOICE_ID_DEFAULT = 'female_01' const callCosyVoice = async (text, voice = VOICE_ID_DEFAULT) => { const apiKey = process.env.DASHSCOPE_API_KEY || '' console.log('[callCosyVoice] apiKey.len=', apiKey.length, 'text=', JSON.stringify(text), 'voice=', voice) if (!apiKey) return { ok: false, error: 'DASHSCOPE_API_KEY 未配置' } const t = String(text || '').trim() if (!t) return { ok: false, error: 'empty text' } const isMale = voice.indexOf('male') >= 0 const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', } const fallbacks = [] fallbacks.push({ name: 'Fallback-A:Qwen3-TTS', url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', body: { model: 'qwen3-tts-flash', input: { text: t, voice: isMale ? 'Cherry' : 'Cherry', language_type: 'Chinese', }, }, }) fallbacks.push({ name: 'Fallback-B:CosyVoice-v3', url: 'https://dashscope.aliyuncs.com/api/v1/services/audio/tts/SpeechSynthesizer', body: { model: 'cosyvoice-v3-flash', input: { text: t, voice: isMale ? 'longxiaocheng' : 'longanyang', format: 'mp3', sample_rate: 24000, }, }, }) for (let idx = 0; idx < fallbacks.length; idx++) { const fb = fallbacks[idx] console.log(' [fb ' + idx + '] try ' + fb.name + ' url=' + fb.url) console.log(' [fb ' + idx + '] body=', JSON.stringify(fb.body)) try { const resp = await global.__builtin_fetch(fb.url, { method: 'POST', headers, body: JSON.stringify(fb.body), }) console.log(' [fb ' + idx + '] resp.status=', resp ? resp.status : 'null', 'resp.ok=', resp ? resp.ok : 'null') if (!resp || !resp.ok) { const txt = resp ? await resp.text().catch(() => '') : '' console.log(' [fb ' + idx + '] !ok resp.text head=', (txt || '').slice(0, 500)) continue } const j = await resp.json() console.log(' [fb ' + idx + '] json keys=', Object.keys(j || {}).join(',')) let audioUrl = '' try { const output = (j && j.output) || null console.log(' [fb ' + idx + '] output keys=', output ? Object.keys(output).join(',') : 'null') if (output) { if (output.audio && typeof output.audio.url === 'string') { audioUrl = output.audio.url; console.log(' [fb ' + idx + '] audioUrl via output.audio.url') } if (!audioUrl && typeof output.audio_url === 'string') { audioUrl = output.audio_url; console.log(' [fb ' + idx + '] audioUrl via output.audio_url') } if (!audioUrl && typeof output.audioUrl === 'string') { audioUrl = output.audioUrl; console.log(' [fb ' + idx + '] audioUrl via output.audioUrl') } if (!audioUrl && Array.isArray(output.results) && output.results[0]) { const first = output.results[0] || {} if (first.audio && typeof first.audio.url === 'string') { audioUrl = first.audio.url; console.log(' [fb ' + idx + '] audioUrl via output.results[0].audio.url') } } } } catch (e) { console.log(' [fb ' + idx + '] output ex=', e.message) } console.log(' [fb ' + idx + '] audioUrl=', audioUrl) if (!audioUrl) continue const download = await global.__builtin_fetch(audioUrl) console.log(' [fb ' + idx + '] download status=', download ? download.status : 'null') if (!download || !download.ok) continue const buf = Buffer.from(await download.arrayBuffer()) const storedName = `rc_${Date.now()}_${crypto.randomBytes(4).toString('hex')}.mp3` const absPath = path.join(TTS_ROOT, storedName) fs.writeFileSync(absPath, buf) const relPath = `uploads/mp_rep_clock/tts_cache/${storedName}` const stat = fs.statSync(absPath) const durationMs = Math.max(1000, Math.round(stat.size / (12 * 1024 / 8))) console.log(' [fb ' + idx + '] OK storedName=' + storedName + ' size=' + stat.size + ' durationMs=' + durationMs) return { ok: true, url: `/${relPath}`, durationMs } } catch (e) { console.log(' [fb ' + idx + '] EXCEPTION=', e && e.stack ? e.stack : String(e)) } } return { ok: false, error: 'All TTS fallbacks failed' } } ;(async () => { console.log('=== callCosyVoice(热身开合跳, female_01) ===') const r = await callCosyVoice('热身开合跳', 'female_01') console.log('\nFINAL RESULT=', JSON.stringify(r, null, 2)) })()