Initial Commit
This commit is contained in:
122
packages/media/audio/voice-elevenlabs/src/tts/tts.md
Normal file
122
packages/media/audio/voice-elevenlabs/src/tts/tts.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# `tts`
|
||||
|
||||
ElevenLabs implementation of `@suaveplan/voice`'s `VoiceSynthesizer`
|
||||
interface. Posts to `/v1/text-to-speech/{voice_id}` for buffered
|
||||
synthesis and the `/stream` variant for incremental streaming.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Application code that depends on `@suaveplan/voice/VoiceSynthesizer`
|
||||
should be portable across providers. This adapter is the seam between
|
||||
the abstract domain interface and ElevenLabs's REST API: it maps the
|
||||
`SynthesisRequest` shape onto the wire body, mirrors voice
|
||||
settings into ElevenLabs's `voice_settings` envelope, validates output
|
||||
formats against the subset ElevenLabs supports, and remaps every
|
||||
ElevenLabs error onto the corresponding `@suaveplan/voice` domain error.
|
||||
|
||||
## Features
|
||||
|
||||
- Implements `synthesize(request)` for buffered audio and
|
||||
`synthesizeStream(request)` for an incremental `ReadableStream<Uint8Array>`.
|
||||
- Eight ElevenLabs-supported `AudioFormat` values are passed through
|
||||
with explicit mapping; unsupported formats throw
|
||||
`AudioFormatUnsupportedError` client-side before the request is sent.
|
||||
- Voice settings (stability, similarity_boost, style, use_speaker_boost)
|
||||
are mapped from the camelCase names to the wire snake_case
|
||||
names with all four optional.
|
||||
- Latency mode 0–4 is forwarded as `optimize_streaming_latency`.
|
||||
- Pronunciation dictionary IDs are wrapped into the
|
||||
`pronunciation_dictionary_locators` envelope ElevenLabs expects.
|
||||
- Errors are remapped: 401 → `ProviderNotConfiguredError`, 429 →
|
||||
`ProviderRateLimitedError` (with `retryAfterMs`), `STREAM.INTERRUPTED`
|
||||
→ `TtsStreamInterruptedError`, everything else →
|
||||
`TtsSynthesisFailedError`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
createElevenLabsAdapters,
|
||||
} from "@suaveplan/third-party-elevenlabs";
|
||||
|
||||
const { synthesizer } = createElevenLabsAdapters({
|
||||
apiKey: process.env.ELEVENLABS_KEY!,
|
||||
});
|
||||
const result = await synthesizer.synthesize({
|
||||
text: "hello world",
|
||||
voiceId: "21m00Tcm4TlvDq8ikWAM",
|
||||
});
|
||||
await Bun.write("hello.mp3", result.audio);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
ElevenLabsClient,
|
||||
ElevenLabsVoiceSynthesizer,
|
||||
} from "@suaveplan/third-party-elevenlabs";
|
||||
|
||||
const client = new ElevenLabsClient({ apiKey: KEY });
|
||||
const synth = new ElevenLabsVoiceSynthesizer(client);
|
||||
|
||||
const stream = synth.synthesizeStream({
|
||||
text: "long passage…",
|
||||
voiceId: "abc",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
languageCode: "en",
|
||||
outputFormat: "pcm_44100",
|
||||
latencyMode: 3,
|
||||
voiceSettings: {
|
||||
stability: 0.4,
|
||||
similarityBoost: 0.6,
|
||||
style: 0.1,
|
||||
speakerBoost: true,
|
||||
},
|
||||
previousText: "Once upon a time …",
|
||||
nextText: "and they lived happily.",
|
||||
seed: 42,
|
||||
pronunciationDictionaryIds: ["dict-en-1"],
|
||||
});
|
||||
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
sink.write(value);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class ElevenLabsVoiceSynthesizer implements VoiceSynthesizer`
|
||||
- `providerName: "elevenlabs"`
|
||||
- `synthesize(request: SynthesisRequest): Promise<SynthesisResult>` —
|
||||
`result.audio` is the raw bytes; `format` is the requested format
|
||||
(defaults to `mp3_44100_128`); `requestId` and `charactersUsed` are
|
||||
populated from response headers when present.
|
||||
- `synthesizeStream(request: SynthesisRequest): ReadableStream<Uint8Array>`
|
||||
— errors during the stream are surfaced via stream `controller.error`.
|
||||
|
||||
Throws (sync from `synthesizeStream` if format is unsupported, async
|
||||
otherwise): `AudioFormatUnsupportedError`, `ProviderNotConfiguredError`,
|
||||
`ProviderRateLimitedError`, `TtsSynthesisFailedError`,
|
||||
`TtsStreamInterruptedError`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The default `outputFormat` for the result is `mp3_44100_128` — this is
|
||||
ElevenLabs's server-side default and matches the bytes you'll actually
|
||||
receive when no format is requested.
|
||||
- `voice_id` is `encodeURIComponent`-escaped because some workspace IDs
|
||||
contain spaces.
|
||||
- Format mapping is a static record rather than a passthrough so that
|
||||
adding a new format requires an explicit code change, not a silent
|
||||
wire-passthrough that ElevenLabs might or might not accept.
|
||||
- The streaming path constructs the `ReadableStream` itself instead of
|
||||
returning the raw response body so that error remapping can happen
|
||||
inside `controller.error` rather than as a synchronous throw.
|
||||
Reference in New Issue
Block a user