(params: GenerateFalAudioParams)
| 82 | } |
| 83 | |
| 84 | export async function generateFalAudio(params: GenerateFalAudioParams): Promise<GeneratedAudio> { |
| 85 | const type: AudioType = params.type || 'speech' |
| 86 | const apiKey = getFalApiKey() |
| 87 | |
| 88 | // Voice cloning: a reference sample routes to a zero-shot clone model (F5-TTS), |
| 89 | // which conditions on the sample (ref_audio_url) and speaks the prompt in that voice. |
| 90 | if (params.voiceSampleDataUri) { |
| 91 | const model = params.model || DEFAULT_CLONE_MODEL |
| 92 | const input: Record<string, unknown> = { |
| 93 | gen_text: params.prompt, |
| 94 | ref_audio_url: params.voiceSampleDataUri, |
| 95 | model_type: 'F5-TTS', |
| 96 | } |
| 97 | const { requestId, data } = await runFalQueue(model, input, apiKey) |
| 98 | const url = extractFalMediaUrl(data, ['audio', 'audio_url', 'audio_file', 'output']) |
| 99 | if (!url) throw new Error('No audio URL in Fal.ai clone response') |
| 100 | const { buffer, contentType } = await downloadFalMedia(url) |
| 101 | const cost = await getFalAICostMetadata({ apiKey, endpointId: model, requestId }) |
| 102 | return { |
| 103 | buffer, |
| 104 | contentType: contentType.startsWith('audio/') ? contentType : 'audio/mpeg', |
| 105 | type: 'speech', |
| 106 | model, |
| 107 | jobId: requestId, |
| 108 | cost, |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | const model = params.model || DEFAULT_AUDIO_MODELS[type] |
| 113 | const input = buildInput(type, params, model) |
| 114 | |
| 115 | // For fal audio models the model ID is the queue endpoint. |
| 116 | const { requestId, data } = await runFalQueue(model, input, apiKey) |
| 117 | const url = extractFalMediaUrl(data, ['audio', 'audio_url', 'audio_file', 'output']) |
| 118 | if (!url) throw new Error('No audio URL in Fal.ai response') |
| 119 | |
| 120 | const { buffer, contentType } = await downloadFalMedia(url) |
| 121 | const cost = await getFalAICostMetadata({ apiKey, endpointId: model, requestId }) |
| 122 | |
| 123 | return { |
| 124 | buffer, |
| 125 | contentType: contentType.startsWith('audio/') ? contentType : 'audio/mpeg', |
| 126 | type, |
| 127 | model, |
| 128 | jobId: requestId, |
| 129 | cost, |
| 130 | } |
| 131 | } |
no test coverage detected