If you know imgproxy, this is that, for audio. Point it at S3 or a directory, name a format in the path, and the proxy renders the variant on demand, streams it to the first requester, and writes it back for cached, range-capable serving.
Every request is a signed GET. No request bodies, no server-side state, and no configuration to add when the ask changes.
Options are ordered key:value segments. They fully describe the output and double as the cache key, so f:opus/br:96 and br:96/f:opus yield the same variant. Build one.
Option order is normalized before it is hashed, so the same set of options is the same cache key however you spell it. This is live: play and the waveform run against a real demo proxy. Toggle norm:ebu or enhance:voice and the waveform is redrawn from a fresh render, because peaks is a format and the chain runs before the peaks are computed (CC0 sources). Sizes in the response table are estimates from the options; if the playground is unreachable, the explorer says so and simulates.
Default mp3. aac is an ADTS stream; m4a is fragmented MP4, cut on duration so it streams rather than waiting for a keyframe that audio never has.
brBitrate in kbps for lossy formats.qVBR quality on the codec's own scale. Mutually exclusive with br.srResample. Defaults to the source rate, capped at 48 kHz for lossy.chDownmix to 1 or 2.bdBit depth, lossless only: 16, 24, or 32f for wav.tTrim, start and optional duration in seconds, decimals fine.fadeIn and out, applied inside the trimmed region.gainStatic gain in dB, signed.normEBU loudness normalization, default -16:-1.5:11. v1 is single pass, which is good enough for previews.enhanceSpeech cleanup in one value: enhance:voice is high-pass, denoise, de-ess, compress, limit. A preset name is pinned to its chain forever, so an improved chain ships as a new value rather than changing what your URL returns.A format, not a separate resource, so it caches and redirects exactly as audio does. Min and max pairs in audiowaveform's own JSON or compact binary, ready to draw a player.
ptsPair count, default 800.pk_fmtjson or dat.chDefaults to 1 here, unlike every other format.expUnix timestamp after which the URL answers 410. Signed, but excluded from the cache key, so per-user short-lived URLs share one cached variant.dlSets Content-Disposition to attachment.cbCache buster, participates in the key./infoFormat, duration, sample rate, channels, bit depth, bitrate, size, tags. A field the prober cannot answer is omitted, never null./healthLiveness. 200 whatever the load, because a busy proxy is a working proxy./ready503 once queue depth hits the threshold, recovering at half of it./metricsPrometheus, on a listener of its own with bounded label values.A source carrying a genuine video stream is refused with 415 before any render starts. It is a reject, not a strip: pulling the audio track out of arbitrary video would make this a free transcoding service at video's cost profile and video's exposure, which is a different product. Embedded cover art is metadata and renders normally.
The same URL is framed differently depending on what is cached, so a client implements both. That is the whole contract, and it does not change with the storage backend or the serve mode.
200 with chunked framing and no Content-Length. Bytes leave as the encoder produces them and are teed to the variant store at the same time. Concurrent requests for one cache key coalesce onto a single render and all subscribe to its stream.
By default a hit is a 302 to a presigned variant URL, and storage serves the ranges. In proxy mode the proxy relays the object itself with Content-Length and Accept-Ranges. Either way seeking works, which is the part players care about.
A variant is fully described by its options, so it can never go stale. Every response states its own cache policy, success or error, and no CDN default gets to decide retention.
Point the container at a directory of real files. It runs unsigned here, with the literal insecure standing in for a signature, which is the mode meant for exactly this first look and nothing more.
docker run --rm -p 4000:4000 \ -e AP_ALLOW_INSECURE=true \ -e AP_LOCAL_ROOT=/audio \ -v /path/to/your/audio:/audio:ro \ ghcr.io/audioproxy/audioproxy:0.6.0
BASE=localhost:4000 SRC='plain/local://track.wav' # 30 s Opus preview at 96 kbps, faded curl -o preview.opus \ "$BASE/insecure/f:opus/br:96/t:0:30/fade:1:1/$SRC" # Waveform min/max pairs for a player UI curl "$BASE/insecure/f:peaks/$SRC" # Duration, sample rate, channels curl "$BASE/insecure/info/$SRC"
The mount is read only on purpose: write access to the root is equivalent to choosing what the proxy will serve, so nothing should have it. Replace insecure mode with signed URLs before anything faces real traffic.
Built by Julian Rubisch, an independent developer in Vienna. Available for Rails and Hotwire work through Rails Reviews.
Other things I make:Better Stimulus,Discover Bandcamp Labels,Crow's Nest,SoundLogo,resonAIte.