serverless-offline-transcribe
v1.0.0
Published
Emulate AWS Transcribe batch jobs locally, backed by OpenAI Whisper, when developing your Serverless project
Downloads
24
Maintainers
Readme
serverless-offline-transcribe
This Serverless-offline plugin emulates Amazon
Transcribe batch jobs on your local machine, so code that calls the Transcribe client runs fully
offline — no AWS account, no network, no cloud cost. Media is read from local S3
(Minio), transcribed by a developer-provided local engine
(OpenAI Whisper), and written back to local S3 in the AWS
Transcribe JSON shape — exactly as serverless-offline-sqs translates SQS calls to ElasticMQ.
Scope (MVP): asynchronous batch jobs (StartTranscriptionJob / GetTranscriptionJob /
ListTranscriptionJobs). Streaming transcription is out of scope for now.
Requirements: Node.js ≥ 20 (the bundled AWS SDK v3 requires it).
Local-dev tool — mind the exposure. Like the sibling emulators the server binds
0.0.0.0by default, so it is reachable from your LAN; sethost: 127.0.0.1to keep it loopback-only. It is also unauthenticated and does not bound concurrency — everyStartTranscriptionJobimmediately launches its own Whisper process. This is fine for local development but is not hardened against untrusted callers or high job volume; do not expose it on an untrusted network.
How it works
The plugin stands up a local HTTP server speaking AWS JSON 1.1 and, in offline:start:init, injects
AWS_ENDPOINT_URL_TRANSCRIBE before the offline Lambda runtime snapshots each function's
environment. Your unmodified Transcribe client therefore resolves to localhost with no
application code change.
StartTranscriptionJob registers the job IN_PROGRESS, returns the AWS job descriptor immediately,
and processes asynchronously: download the media from local S3 → run Whisper with word-level
timestamps → shape the result into the AWS Transcribe JSON (word items[], punctuation split into
its own untimed items, audio_segments) → upload it to the resolved output location. GetTranscriptionJob
reports COMPLETED (with Transcript.TranscriptFileUri) or FAILED (with FailureReason).
Prerequisites
- OpenAI Whisper on your
PATH:
Ifpip install -U openai-whisper # also needs ffmpeg (e.g. `brew install ffmpeg`) which whisper # must resolvewhisperis missing, the plugin fails fast at startup naming the prerequisite. - Local S3 (Minio) — the same local S3 this repo uses for
serverless-offline-s3:docker run -p 9000:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \ minio/minio server /data
Neither engine is bundled (developer prerequisites, like ElasticMQ for SQS).
Installation
npm install --save-dev serverless-offline-transcribeAdd it to your serverless.yml plugins before serverless-offline:
plugins:
- serverless-offline-transcribe
- serverless-offlineConfiguration
custom:
serverless-offline-transcribe:
enabled: true # set false (or "false") to skip the emulator entirely
host: 0.0.0.0
port: 4569
accountId: '000000000000'
model: base # Whisper model: tiny | base | small | medium | large
# Local S3 (Minio) — mirrors serverless-offline-s3 config keys
endpoint: http://localhost:9000
region: us-east-1
accessKey: minioadmin
secretKey: minioadmin
# whisperBin: whisper # optional: path to the whisper binary
# whisperTimeout: 600000 # optional: per-job Whisper timeout (ms)| Option | Default | Description |
| --- | --- | --- |
| enabled | true | false / "false" skips standing up the server. |
| host / port | 0.0.0.0 / 4569 | Bind address of the local Transcribe endpoint. |
| accountId | 000000000000 | Emitted on the transcript document. |
| model | base | Whisper model size. |
| endpoint | http://localhost:9000 | Local S3 (Minio) endpoint. Path-style addressing is forced (mandatory for Minio). |
| region | us-east-1 | S3 region (Minio ignores the value but the v3 SDK requires one). |
| accessKey / secretKey | minioadmin | Minio credentials (accessKeyId/secretAccessKey also accepted). |
| whisperBin | whisper | Whisper executable (looked up on PATH). |
| whisperTimeout | — | Optional per-job Whisper timeout in ms. |
Behavior notes
- Lifecycle (AC-C1/C2):
StartTranscriptionJobreturnsIN_PROGRESSwithout blocking; processing runs asynchronously;GetTranscriptionJobreports the transition toCOMPLETED/FAILED. - Output location (AC-C3): resolved from
OutputBucketName+OutputKey(ends.json→ verbatim; ends/→{OutputKey}{jobName}.json; absent →{jobName}.json).Transcript.TranscriptFileUriis a path-style Miniohttp://URL. WhenOutputBucketNameis absent it falls back to the media bucket. - Failures (AC-C4/C5): an unsupported language/format, a run that recognizes no speech, or a bad
media URI fails the job with a
FailureReason(never a silently-empty transcript). A missing Whisper binary fails fast at startup. - Security: Whisper is invoked via
execFile(no shell) with the audio path as an argument. - These are local-development / CI tools: the goal is a protocol-faithful substitute, not AWS Transcribe accuracy parity. Diarization / custom vocabularies / redaction are non-goals.
Copy-paste example
service: my-service
plugins:
- serverless-offline-transcribe
- serverless-offline
provider:
name: aws
runtime: nodejs18.x
custom:
serverless-offline-transcribe:
model: base
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
functions:
transcribe:
handler: handler.transcribe
events:
- httpApi: 'POST /transcribe'// handler.js — unchanged production code
const {
TranscribeClient,
StartTranscriptionJobCommand,
GetTranscriptionJobCommand
} = require('@aws-sdk/client-transcribe');
const client = new TranscribeClient({}); // resolves to localhost:4569 offline
exports.transcribe = async event => {
const {jobName, mediaUri} = JSON.parse(event.body);
await client.send(
new StartTranscriptionJobCommand({
TranscriptionJobName: jobName,
LanguageCode: 'en-US',
Media: {MediaFileUri: mediaUri}, // s3://my-bucket/audio.wav in local Minio
OutputBucketName: 'my-bucket',
OutputKey: 'transcripts/'
})
);
const {TranscriptionJob} = await client.send(
new GetTranscriptionJobCommand({TranscriptionJobName: jobName})
);
return {statusCode: 200, body: JSON.stringify(TranscriptionJob)};
};# with Minio + whisper running, and an audio object uploaded to s3://my-bucket/audio.wav
serverless offlineLicense
MIT
