defmodule AgentSea.Voice.ElevenLabs do
@moduledoc """
ElevenLabs text-to-speech adapter (`POST /v1/text-to-speech/:voice_id`) over
`Req`. Implements `AgentSea.Voice.TTS`.
Options: `:api_key` (defaults to `ELEVENLABS_API_KEY`), `:voice` (voice id),
`:model`, `:base_url`, and `:adapter` (a Req adapter, to stub HTTP in tests).
"""
@behaviour AgentSea.Voice.TTS
@base_url "https://api.elevenlabs.io"
# "Rachel" — ElevenLabs' default sample voice.
@default_voice "21m00Tcm4TlvDq8ikWAM"
@impl true
def synthesize(text, opts) do
voice = opts[:voice] || @default_voice
body = %{text: text, model_id: opts[:model] || "eleven_multilingual_v2"}
case Req.post(req(opts), url: "/v1/text-to-speech/#{voice}", json: body) do
{:ok, %Req.Response{status: 200, body: audio}} when is_binary(audio) ->
{:ok, %{audio: audio, format: "mp3"}}
{:ok, %Req.Response{status: status, body: resp}} ->
{:error, {:http_error, status, resp}}
{:error, reason} ->
{:error, reason}
end
end
defp req(opts) do
api_key = opts[:api_key] || System.get_env("ELEVENLABS_API_KEY")
[
base_url: opts[:base_url] || @base_url,
headers: [{"xi-api-key", api_key || ""}]
]
|> maybe_put(:adapter, opts[:adapter])
|> Req.new()
end
defp maybe_put(kw, _key, nil), do: kw
defp maybe_put(kw, key, value), do: Keyword.put(kw, key, value)
end