前提:在cf的workers中部署,提前免费申请Fish Audio API的key,因为这玩意是免费10$额度,所以随便找几个临时邮箱多搞几个key,下面代码支持环境变量多个key,这不是美滋滋?直接TM狠狠白嫖
在环境变量中添加 FISHKEY,值为您的多个 Fish Audio API 密钥,用英文逗号分隔。例如:
key1,key2,key3
在环境变量中添加 TTSKEY
,值为您想要用于认证的密钥。【这个是你设置的key】
下面代码实现效果:
效果:兼容openai格式
curl https://你的域名 \
-H "Authorization: Bearer 123" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"input": "你好.",
"voice": "56d7171beaa1418c9c16301c48809f90"
}' \
--output 112233.mp3
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
function getRandomApiKey() {
const keys = FISHKEY.split(',').map(key => key.trim())
return keys[Math.floor(Math.random() * keys.length)]
}
async function handleRequest(request) {
// Handle CORS preflight request
if (request.method === 'OPTIONS') {
return handleCORS(request)
}
const apiUrl = 'https://api.fish.audio/v1/tts'
// Check for proper authentication
const authHeader = request.headers.get('Authorization')
if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== TTSKEY) {
return new Response('Unauthorized', { status: 401 })
}
if (request.method !== 'POST') {
return new Response('Please send a POST request.', { status: 405 })
}
try {
let reqBody
try {
reqBody = await request.json()
} catch (error) {
return new Response(JSON.stringify({ error: 'Invalid JSON in request body' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
})
}
console.log('Request body:', JSON.stringify(reqBody))
// Map OpenAI format to Fish Audio format
const fishAudioBody = {
text: reqBody.input || '你好啊',
reference_id: reqBody.voice || '56d7171beaa1418c9c16301c48809f90'
}
const apiKey = getRandomApiKey()
const fishAudioResponse = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(fishAudioBody)
})
console.log('Fish Audio API response status:', fishAudioResponse.status)
console.log('Fish Audio API response content-type:', fishAudioResponse.headers.get('content-type'))
if (!fishAudioResponse.ok) {
const errorText = await fishAudioResponse.text()
console.error('Fish Audio API error:', errorText)
return new Response(JSON.stringify({ error: `API error: ${errorText}` }), {
status: fishAudioResponse.status,
headers: getCORSHeaders({
'Content-Type': 'application/json'
})
})
}
const contentType = fishAudioResponse.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
const fishAudioData = await fishAudioResponse.json()
console.log('Fish Audio API response:', JSON.stringify(fishAudioData))
if (!fishAudioData.audio_url) {
return new Response(JSON.stringify({ error: 'No audio URL in the API response' }), {
status: 500,
headers: getCORSHeaders({
'Content-Type': 'application/json'
})
})
}
const mp3Response = await fetch(fishAudioData.audio_url)
console.log('MP3 fetch response status:', mp3Response.status)
if (!mp3Response.ok) {
return new Response(JSON.stringify({ error: 'Failed to fetch MP3 file' }), {
status: mp3Response.status,
headers: getCORSHeaders({
'Content-Type': 'application/json'
})
})
}
const mp3Data = await mp3Response.arrayBuffer()
return new Response(mp3Data, {
headers: getCORSHeaders({
'Content-Type': 'audio/mpeg',
'Content-Disposition': 'attachment; filename="tts_output.mp3"'
})
})
} else {
// If the response is not JSON, assume it's the audio file directly
const audioData = await fishAudioResponse.arrayBuffer()
return new Response(audioData, {
headers: getCORSHeaders({
'Content-Type': 'audio/mpeg',
'Content-Disposition': 'attachment; filename="tts_output.mp3"'
})
})
}
} catch (error) {
console.error('Unexpected error:', error)
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: getCORSHeaders({
'Content-Type': 'application/json'
})
})
}
}
function handleCORS(request) {
// Make sure the necessary headers are present
// for this to be a valid pre-flight request
let headers = request.headers
if (
headers.get('Origin') !== null &&
headers.get('Access-Control-Request-Method') !== null &&
headers.get('Access-Control-Request-Headers') !== null
) {
// Handle CORS pre-flight request.
// If you want to check the requested method + headers
// you can do that here.
return new Response(null, {
headers: getCORSHeaders(),
})
} else {
// Handle standard OPTIONS request.
// If you want to allow other HTTP Methods, you can do that here.
return new Response(null, {
headers: {
Allow: 'GET, HEAD, POST, OPTIONS',
},
})
}
}
function getCORSHeaders(additionalHeaders = {}) {
return {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
...additionalHeaders,
}
}