Skip to main content

stapel-recordings

CI coverage pypi downloads python license llms.txt

An audio service, not a video host. Recording lifecycle: upload of any media container (presigned single-PUT and multipart), from which the audio track is extracted, downmixed to mono and stored — the container itself is never kept and never served back — then a configurable processing pipeline (convert, transcribe, diarize, merge) producing a unified speaker-attributed transcript, optional automatic summaries, and a watchdog that recovers stuck or abandoned recordings. Storage therefore scales with hours of speech (~10.8 MB/hour at the default Opus profile), not with what the recording was made on.

Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.

Install

pip install stapel-recordings

At a glance

Fact Value
Version 0.22.1
Python >=3.11 (3.11, 3.12, 3.13, 3.14)
HTTP operations 12
Config axes 1
Usage surface 42
Extension points 7
Error codes 61
Fleet dependencies stapel-agent (optional) · stapel-auth (optional) · stapel-core

Documentation

OpenAPI · capabilities.json · llms.txt (for agents)

What this is

An audio service, not a video host. Upload whatever your users record on — a screen capture, a phone video, a voice memo — and this module keeps the audio track and only the audio track: extracted, downmixed to mono, stored once. The container is transport. It is deleted as soon as its audio is out, there is no field and no URL that hands it back, and that holds for a 4 GB screen recording and a 3 MB voice memo alike. A host's storage bill therefore scales with hours of speech (~10.8 MB/hour at the default Opus profile), not with what the recording happened to be made on.

Owns the lifecycle capture/upload → storage → transcribe → summarize: Recording + Speaker + Segment (the unified transcript), presigned / multipart upload sessions, and a data-driven, outbox-backed processing pipeline with retry, DLQ and reconcile.

Speech-to-text and summarization are delegated to stapel-agent via the llm.transcribe / llm.summarize comm Functions — this module does not implement STT or LLM calls. Object storage goes through a swappable seam.

Uploads: two ceilings, because two different things

Because the container is discarded, "what we accept" and "what we keep" are different questions and one number cannot answer both:

Setting Default Bounds
MAX_CONTAINER_UPLOAD_BYTES 16 GiB what we are willing to receive and run through extraction — bandwidth, temp disk and ffmpeg time
MAX_STORED_BYTES 512 MiB what we are willing to keep, enforced on the extracted audio (≈47 h at the Opus profile)
MAX_UPLOAD_BYTES 2 GiB the ceiling when nothing is extracted (AUDIO_ONLY_INGEST = False), where received and stored are the same object

services.accepted_upload_limit() picks between the first and the last, and it is that number the 413 quotes. It fails safe: drop convert from the PIPELINE, or point NORMALIZER at passthrough_normalize, and the accepted ceiling falls back to the storage-shaped one on its own — the raised limit cannot outlive the promise that made it large. System check stapel_recordings.E007 tells the operator they are in that state, and E006 refuses a deploy whose ffmpeg is missing or has no libopus.

Read the limits before uploading, so a client refuses an oversized file locally and names the real number:

GET /recordings/api/v1/recordings/upload-limits
{"max_upload_bytes": 17179869184, "max_stored_bytes": 536870912,
 "audio_only_ingest": true, "stored_audio_codec": "opus",
 "stored_audio_channels": 1, "stored_audio_sample_rate": 16000,
 "stored_bytes_per_hour": 10800000, "multipart_part_size": 10485760,
 "max_multipart_parts": 10000, "allowed_extensions": ["3gp", "aac", …]}

A refusal is an answer, not a 500: every upload error this module raises is a DRF-aware StapelServiceError, so a host view that calls services.start_multipart_upload directly answers 413 error.413.recording_too_large with {size, limit} in the standard envelope, with no try/except of its own (400 for a size that is not a size or a malformed part list, 415 for the file type, 409 for nothing stored, 503 when the content gate could not run).

The stored profile

Mono, 16 kHz, Ogg/Opus at 24 kbps — AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, AUDIO_CODEC, AUDIO_BITRATE_BPS. Mono is the default because every downstream consumer here reads a single mixed track: diarization is the ASR provider separating speakers within it, not channel separation, and this module has downmixed since its first release. A host whose provider does separate by channel sets AUDIO_CHANNELS = 2 and pays for it in bytes. AUDIO_CODEC = "wav" restores 16-bit PCM (~115 MB/hour) for a provider that will not take Opus.

Every upload is re-encoded to the profile, including one that arrives as audio already: a "this one is fine as it is" branch would have to be right about container, codec, channel layout and sample rate at once, and it would make the stored bytes depend on what the client happened to send.

To keep originals anyway — a documented exception, off by default — set AUDIO_ONLY_INGEST = False; the accepted ceiling drops to MAX_UPLOAD_BYTES in the same move.

python manage.py recordings_audio_census reports, read-only, how many recordings still hold an uploaded container, what they weigh, and what the same recordings would occupy as mono audio.

Quick start

The base install uses the Django-storage backend; add the s3 extra for the boto3 S3/MinIO backend:

pip install "stapel-recordings[s3]"
INSTALLED_APPS = [
    # ...
    "stapel_core.django.outbox",   # transactional outbox (pipeline reliability)
    "stapel_recordings",
]

# urls.py
path("recordings/", include("stapel_recordings.urls"))

The transcribe / merge stages call stapel-agent by comm name — install and configure stapel-agent (or provide llm.transcribe / llm.summarize providers) for the pipeline to complete. The default convert stage needs ffmpeg/ffprobe on PATH (or set NORMALIZER to passthrough_normalize).

The pipeline is data you can edit

STAPEL_RECORDINGS = {
    # Reorder / subset / insert stages — no fork:
    "PIPELINE": ["convert", "transcribe", "redact_pii", "merge"],
    # Replace or add stage handlers (merge-over-builtins; None removes):
    "STAGES": {"diarize": "myproject.stages.PyannoteDiarizer"},
    # Or source the list at runtime (DB / per-workspace / per-recording):
    "PIPELINE_RESOLVER": "myproject.pipelines.resolve",
    # Swap the object store:
    "STORAGE": "stapel_recordings.storage.S3Backend",
}

A generic driver runs the resolved stage list, advancing the status machine and emitting the next stage through the outbox. See MODULE.md for the stage contract and worked examples.

Settings

All configuration lives in the STAPEL_RECORDINGS namespace (dict setting, flat setting, or env var — resolved lazily). See the full table in MODULE.md. Highlights: PIPELINE, STAGES, PIPELINE_RESOLVER, STORAGE, NORMALIZER, SUMMARIZE_ENABLED, MAX_STAGE_RETRIES.

comm surface

Kind Name Contract
Action (emit) recording.uploaded, recording.stage_completed, recording.completed, recording.failed pipeline lifecycle (public); the run events carry run_id + attempt — a reprocess is a new run, so meter on recording_id + run_id
Action (consume) recording.uploaded, recording.stage, user.deleted driver + GDPR erase
Function (call) llm.transcribe, llm.summarize provided by stapel-agent

Operations

python manage.py recordings_reconcile --once   # re-drive stuck recordings

Development

pip install -e . && pip install pytest pytest-django ruff jsonschema djangorestframework
./setup-hooks.sh
pytest tests/

License

MIT — see LICENSE.


This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stapel_recordings-0.22.1.tar.gz (307.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stapel_recordings-0.22.1-py3-none-any.whl (240.8 kB view details)

Uploaded Python 3

File details

Details for the file stapel_recordings-0.22.1.tar.gz.

File metadata

  • Download URL: stapel_recordings-0.22.1.tar.gz
  • Upload date:
  • Size: 307.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_recordings-0.22.1.tar.gz
Algorithm Hash digest
SHA256 ada4050119f1cde56e2cc018a2c7241540ecef96c6a9d89036cb691e89f1a4e0
MD5 ae3c38ece2c3cee5ebb240454ed4d1eb
BLAKE2b-256 d2be993ed605ab815f1415b9a4c22ea765c7970d0c0925633ac802fe9d7c39b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_recordings-0.22.1.tar.gz:

Publisher: publish.yml on usestapel/stapel-recordings

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stapel_recordings-0.22.1-py3-none-any.whl.

File metadata

File hashes

Hashes for stapel_recordings-0.22.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f99a1edf6801aadc303eb7f36b64f196d80d92a8e25bb60e098def9698136285
MD5 9d89b0628c14005a5c238780a1a9bb0b
BLAKE2b-256 711524aedeaedf4757726c96889d56a9deb276468735ad9531c15fb3b7b2ea89

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_recordings-0.22.1-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-recordings

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.25.0

2 files

0.24.0

2 files

0.23.1

2 files

0.23.0

2 files

This release

0.22.1 This release

2 files

0.22.0

2 files

0.21.1

2 files

0.21.0

2 files

0.20.2

2 files

0.20.1

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.1

2 files

0.16.1

2 files

0.15.0

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.1

2 files

0.13.0

2 files

0.12.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.2

2 files

0.5.1

2 files

0.4.4

2 files

0.4.3

2 files

0.4.0

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.2.1

2 files

0.1.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page