# AWS Polly

> Text-to-speech from Amazon Polly with AWSPollyTTSService, with SSML prosody, engines and lexicons.

Source: https://polvallverdu.github.io/pipedog/docs/services/tts/aws/



## Overview [#overview]

`AWSPollyTTSService` synthesizes each sentence with Amazon Polly's `SynthesizeSpeech` API. It
wraps the text in SSML (with prosody when `rate`, `pitch` or `volume` are set), requests 16 kHz
PCM and resamples it to the pipeline's output rate.

<Cards>
  <Card title="Upstream page" href="https://docs.pipecat.ai/api-reference/server/services/tts/aws">
    Pipecat's documentation for the same service.
  </Card>

  <Card title="Source" href="https://github.com/polvallverdu/pipedog/tree/main/packages/aws/src">
    `packages/aws/src/tts.ts`.
  </Card>

  <Card title="Polly docs" href="https://docs.aws.amazon.com/polly/latest/dg/voicelist.html">
    Voices, engines and SSML.
  </Card>
</Cards>

## Installation [#installation]

```bash
bun add @pipedog/aws
```

```ts
import { AWSPollyTTSService } from "@pipedog/aws/tts";
```

## Prerequisites [#prerequisites]

* An IAM identity allowed `polly:SynthesizeSpeech`.
* Keys, in this order of precedence:

  1. `awsAccessKeyId`, `apiKey` (the secret access key) and `awsSessionToken`.
  2. The `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment
     variables.
  3. A `credentialsProvider` function, called for every request.

  The region comes from `region`, else `AWS_REGION`, else `us-east-1`.

## AWSPollyTTSService [#awspollyttsservice]

### Configuration [#configuration]

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string \| null \| undefined` | no | AWS secret access key. If null, falls back to environment variables and `credentialsProvider`. |
| `awsAccessKeyId` | `string \| null \| undefined` | no | AWS access key ID. Same fallback behaviour as `apiKey`. |
| `awsSessionToken` | `string \| null \| undefined` | no | AWS session token for temporary credentials. |
| `region` | `string \| null \| undefined` | no | AWS region for Polly service. Defaults to 'us-east-1'. |
| `settings` | `AWSPollyTTSSettings \| null \| undefined` | no | Runtime-updatable settings. |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |
| `enableDirectMode` | `boolean \| undefined` | no | Process frames immediately instead of using internal queues. |
| `metrics` | `FrameProcessorMetrics \| null \| undefined` | no | Optional metrics collector for this processor. |
| `textAggregationMode` | `TextAggregationMode \| null \| undefined` | no | How to aggregate incoming text before synthesis. `TextAggregationMode.SENTENCE` (default) buffers until sentence boundaries, `TextAggregationMode.TOKEN` streams tokens directly for lower latency. |
| `pushTextFrames` | `boolean \| undefined` | no | Whether to push TextFrames and LLMFullResponseEndFrames. |
| `pushStopFrames` | `boolean \| undefined` | no | Whether to automatically push TTSStoppedFrames. |
| `pushStartFrame` | `boolean \| undefined` | no | Whether to automatically create audio contexts and push TTSStartedFrames. When true, the base class handles `createAudioContext` and yields `TTSStartedFrame` before each synthesis call, so `runTts` implementations do not need to. |
| `stopFrameTimeoutS` | `number \| undefined` | no | Idle time before pushing TTSStoppedFrame when pushStopFrames is true. |
| `pushSilenceAfterStop` | `boolean \| undefined` | no | Whether to push silence audio after TTSStoppedFrame. |
| `silenceTimeS` | `number \| undefined` | no | Duration of silence to push when pushSilenceAfterStop is true. |
| `pauseFrameProcessing` | `boolean \| undefined` | no | Whether to hold incoming frames after a turn until its audio contexts end. Needed by services whose audio doesn't carry its request's context ID. |
| `maxConsecutiveZeroAudioContexts` | `number \| undefined` | no | How many consecutive TTS contexts may complete without producing any audio before the service is reported unable to do its job. Catches a provider that accepts requests and stays silent — an unknown voice ID, say — which no error ever surfaces. Every silent context reports an error the service can carry on from, whatever the limit is; on reaching the limit the service reports a permanent error instead, stops being given work, and the pipeline worker applies its `ProcessorUnusablePolicy`. Set to 0 to report silent contexts without ever writing the service off. |
| `appendTrailingSpace` | `boolean \| undefined` | no | Whether to append a trailing space to text before sending to TTS. This helps prevent some TTS services from vocalizing trailing punctuation (e.g., "dot"). Only applied in sentence aggregation mode; when streaming tokens, the incoming text's own whitespace is preserved. |
| `sampleRate` | `number \| null \| undefined` | no | Output sample rate for generated audio. |
| `skipTextTypes` | `string[] \| null \| undefined` | no | List of text types that should not be spoken. |
| `textTransforms` | `[string, TextTransform][] \| null \| undefined` | no | A list of TextTransforms to transform text just before sending it to TTS. Each TextTransform takes the aggregated text and its type, and returns the transformed text. To register, provide a list of tuples of `[textType \| "*", transformFunction]`. |
| `transportDestination` | `string \| null \| undefined` | no | Destination for generated audio frames. |
| `reuseContextIdWithinTurn` | `boolean \| undefined` | no | Whether the service should reuse context IDs within the same turn. |
| `audioCache` | `TTSAudioCacheStore \| null \| undefined` | no | Where to keep synthesized audio for reuse (e.g. a shared `TTSAudioCache`). Text whose audio is in the cache is served from it, with no request to the provider. Lines get there through `warmAudioCache()`, and also from live synthesis when `cacheSynthesizedAudio` allows it. Only services that yield their audio from `runTts` (HTTP and streaming-RPC services, see `supportsAudioCache`) use it, and only in sentence aggregation mode. Defaults to null (no cache). pipedog only. |
| `cacheSynthesizedAudio` | `boolean \| ((text: string) => boolean) \| undefined` | no | Whether audio synthesized live is stored in `audioCache`, or a function that decides per text. Defaults to false: only warmed lines are stored, since a generated reply can repeat what the user said. pipedog only. |
| `audioCacheNamespace` | `string \| undefined` | no | Extra part of the cache key, for constructor options that change the audio but are not in `settings`. pipedog only. |
| `credentialsProvider` | `AWSCredentialsProvider \| null \| undefined` | no | Keys to sign with when none are passed in or set in the environment. Called for every request or connection, so it can renew temporary credentials. |
| `fetch` | `FetchLike \| null \| undefined` | no | `fetch` used for HTTP calls (defaults to the global `fetch`). |
| `endpointUrl` | `string \| null \| undefined` | no | Override the service endpoint (e.g. a VPC endpoint or a local fake), `https://host[:port]`. |

### Settings [#settings]

Runtime-updatable settings, passed as `settings: new AWSPollyTTSService.Settings({...})`.

| Setting        | Type               | Default    | Description                                                                             |
| -------------- | ------------------ | ---------- | --------------------------------------------------------------------------------------- |
| `voice`        | `string`           | `"Joanna"` | Polly voice ID.                                                                         |
| `language`     | `string`           | `"en-US"`  | Polly language code. A `Language` is mapped with `languageToAwsLanguage`.               |
| `engine`       | `string \| null`   | `null`     | `"standard"`, `"neural"`, `"long-form"` or `"generative"`. `null` uses Polly's default. |
| `rate`         | `string \| null`   | `null`     | SSML prosody rate, e.g. `"110%"` or `"slow"`.                                           |
| `pitch`        | `string \| null`   | `null`     | SSML prosody pitch (standard engine only).                                              |
| `volume`       | `string \| null`   | `null`     | SSML prosody volume, e.g. `"loud"` or `"+3dB"`.                                         |
| `lexiconNames` | `string[] \| null` | `null`     | Pronunciation lexicons to apply.                                                        |

### Usage [#usage]

```ts title="tts.ts"
import { AWSPollyTTSService } from "@pipedog/aws/tts";
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";

const tts = new AWSPollyTTSService({
  // Or leave these out to read the AWS_* environment variables.
  awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
  apiKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: "us-east-1",
  sampleRate: 24000,
  settings: new AWSPollyTTSService.Settings({
    voice: "Joanna",
    engine: "generative",
    language: "en-US",
    rate: "105%",
  }),
});
```

Change the voice mid-call with a `TTSUpdateSettingsFrame`:

```ts
// Queue it on the worker, or push it from a processor upstream of the service.
const update = new TTSUpdateSettingsFrame({
  delta: new AWSPollyTTSService.Settings({ voice: "Matthew" }),
});
```

### Notes [#notes]

* One HTTP request per sentence. The audio is pushed as `TTSAudioRawFrame`s of `chunkSize`
  bytes once the response has arrived.
* Text is XML-escaped and sent as SSML (`TextType: "ssml"`).
* Any failed request (an HTTP error, a network error, missing credentials) becomes an
  `ErrorFrame`. Upstream does this for botocore errors only.

## Runtime notes [#runtime-notes]

| Runtime            | Works | What it needs |
| ------------------ | ----- | ------------- |
| Bun                | ✅ yes | Nothing.      |
| Node.js            | ✅ yes | Nothing.      |
| Deno               | ✅ yes | Nothing.      |
| Cloudflare Workers | ✅ yes | Nothing.      |

Generated from `pipecat.runtimes` and `pipecat.compat` in `packages/aws/package.json`: ✅ means the compat smoke test passes on that runtime.

* Runs on Bun, Node, Deno and Cloudflare Workers: `POST /v1/speech` with `fetch`, signed with
  SigV4 on WebCrypto.
* There is no default AWS credential chain (profiles, IMDS, ECS roles, SSO). Use keys, the
  environment, or `credentialsProvider`.
* `endpointUrl` and `fetch` replace the Polly host and the global `fetch`.
* Not ported: the deprecated `InputParams`, and the `voiceId` and `params` constructor options.
  Use `settings`.
