Transcribe it. Fix it. Style it. Burn it — or automate the whole folder.
A self-hosted subtitle production workbench built around Rust, SvelteKit, FFmpeg/libass and your own transcription provider.
AutoSubs is for the part that happens after you have a video: get word timings from Whisper/Speaches or import an existing subtitle file, clean up the text, repair bad timing, style it once, preview the right canvas, and let FFmpeg burn it with libass. For recurring work, a Workflow watches a folder and runs the same pipeline automatically.
It is deliberately not a browser-only subtitle toy. The Rust backend owns timing normalization, segmentation, persistence, rendering and workflow state. The web UI is a client of that API, so manual jobs and automated jobs go through the same rules.
Queue — local/resumable uploads and server-side files feed the same persistent job system.
Editor — seekable video preview, subtitle list, canonical regrouping, timing tools and subtitle-only exports.
The interface is designed for desktop, tablet and phone — not just squeezed into a smaller viewport.
- Video ingest without a tiny extension allow-list — AutoSubs asks
ffprobewhether a stable file actually contains video. - Resumable browser uploads — tus 1.0-style
HEAD/PATCHuploads resume after network loss; re-selecting the same file after a reload resumes from the server offset. - Server-side picker — use files already mounted into the container instead of uploading them again.
- Sidecar-first workflow — import
.ass,.ssa,.srtor AutoSubs JSON; attach, replace or detach a sidecar before rendering. - Transcription providers — OpenAI-compatible transcription endpoints plus an optional local provider/fallback such as Speaches.
- Optional LLM correction — spelling/punctuation correction after import/transcription while preserving line count and timings.
- Canonical subtitle timing engine — one Rust implementation repairs invalid ranges, overlaps, gaps and word timings. The browser does not maintain a competing copy.
- Unicode-aware line grouping — grapheme counting, Unicode line-break opportunities and French no-break rules instead of UTF-8 byte counting.
- ASS/libass styling — Pop, Karaoke, Fade, Bounce, Slide-up, Floating and plain subtitle modes; custom fonts, outline, shadow, placement and highlight colors.
- Real format profiles — Source, 9:16, 16:9, 1:1, 4:5 and custom canvases. Source geometry is preserved by default;
contain,coverandstretchare explicit choices. - Brands — group logo/outro assets and choose a default preset per output format.
- Workflows — independent watch/output/archive folders, Brand/preset resolution, native filesystem events plus periodic reconciliation for NFS-mounted folders.
- No archive-on-failure — a source is archived only after the final video and subtitle sidecars have been published successfully.
- Transactional output publication — existing outputs are kept recoverable until the new video + SRT + ASS + JSON set is committed.
- Persistent jobs — SQLite keeps queue state, settings, workflows, assets and events across restarts. Active jobs become
interruptedafter an unexpected restart instead of pretending they completed. - Real cancellation — waiting jobs and running FFmpeg/network work use cancellation tokens; a cancelled job does not later consume a freed encode slot.
- Machine-readable render progress — progress comes from FFmpeg's
-progressprotocol, not regexes against human stderr output. - Hardware encoder discovery — AutoSubs probes the FFmpeg build and can select NVENC, QSV, VA-API, AMF or libx264 according to what is actually present.
autofalls back once to libx264 if hardware launch fails. - Outro normalization — main video and outro are normalized into one concat graph so different dimensions/FPS/audio layouts do not require a fragile stream-copy concat.
- EN / FR UI — instant browser-local language switch. This is separate from the transcription language setting.
The pre-built image is published as ghcr.io/godsquantum/autosubs:latest.
mkdir autosubs && cd autosubs
curl -O https://raw.githubusercontent.com/GodsQuantum/AutoSubs/main/compose.example.yaml
curl -O https://raw.githubusercontent.com/GodsQuantum/AutoSubs/main/.env.example
cp .env.example .env
mkdir -p config data fonts media
# adjust MEDIA_PATH / bind mounts as needed, then:
docker compose -f compose.example.yaml up -dOpen http://<server-ip>:3051.
/config contains autosubs.db and must be local storage. AutoSubs uses SQLite WAL and refuses known network filesystems such as NFS/CIFS/SSHFS for the database path. Your videos, watch folders and outputs can absolutely live on NFS; mount them separately under an allowed media root.
A typical layout is:
/config local SSD / host filesystem — SQLite only
/data local or fast app working data — uploads/jobs/renders
/fonts custom fonts, read-only is fine
/srv/media/... large source/output/archive trees
If your media already lives on a NAS, mount the NAS once and expose only the roots AutoSubs is allowed to browse:
services:
autosubs:
image: ghcr.io/godsquantum/autosubs:latest
container_name: autosubs
init: true
restart: unless-stopped
user: "1000:1000"
ports:
- "3051:3000"
environment:
TZ: UTC
AUTOSUBS_CONFIG_DIR: /config
AUTOSUBS_DATA_DIR: /data
AUTOSUBS_FONTS_DIR: /fonts
AUTOSUBS_ALLOWED_ROOTS: /data:/srv/media
AUTOSUBS_MAX_RENDER_JOBS: "2"
AUTOSUBS_MAX_TRANSCRIPTION_JOBS: "2"
AUTOSUBS_LOCAL_TRANSCRIPTION_ENABLED: "true"
AUTOSUBS_LOCAL_TRANSCRIPTION_URL: http://transcriber:8000/v1/audio/transcriptions
volumes:
- ./config:/config
- ./data:/data
- ./fonts:/fonts:ro
- /srv/media:/srv/mediaProvider environment variables bootstrap an empty database only. After first start, Settings in the UI are authoritative. That avoids a container restart unexpectedly overwriting a key/URL you changed from the UI.
For migration, the old SPEACHES_URL variable is still accepted as a first-boot alias for AUTOSUBS_LOCAL_TRANSCRIPTION_URL.
- Add a local video, choose a server-side video, or pair a video with
.srt/.ass/.ssa/.json. - AutoSubs probes the media and imports or generates subtitle word timings.
- Optional LLM correction runs on text only.
- The canonical Rust engine normalizes timings and grouping.
- The job reaches Ready. Nothing has been re-encoded yet unless you explicitly chose immediate render.
- Review/edit/search/replace/regroup/shift timings in Editor.
- Export SRT/ASS/JSON without touching the video, or click Render video.
- FFmpeg/libass renders to a
.partialstaging file..partialis internal media staging and is never treated as a subtitle file. - Video and sidecars publish together; an optional source archive happens last.
That Ready step is intentional: correcting three words should not cost another full encode just to inspect the result.
A Preset owns visual subtitle behavior: typography, animation, colors, placement, segmentation limits, fit mode, target format and optional outro override.
A Brand can own a logo, default outro and one default preset for each format. A Workflow resolves its style in this order:
explicit workflow preset
↓
brand default for the workflow format
↓
global/default preset resolution
The Job's selected output format remains authoritative. Picking a preset does not silently turn a 16:9 job back into 9:16.
Workflows combine low-latency filesystem events with periodic reconciliation. This matters on NFS: a remote write does not necessarily produce the local inotify event you expected.
Before claiming a candidate AutoSubs checks that its size/mtime stay stable, probes it with ffprobe, deduplicates it persistently, then looks for matching sidecars in priority order:
.ass / .ssa → .srt → .json → transcription
If any preparation or render step fails, the original source stays where it was.
The runtime image includes FFmpeg with libass. On startup AutoSubs probes filters, hardware accelerators and H.264 encoders. The Settings page shows what this particular container can actually use.
For Intel/AMD Linux acceleration, expose /dev/dri to the container and add the host video/render groups as required by your distro. For NVIDIA, use the NVIDIA Container Toolkit and expose the GPU in your Compose stack. Hardware access is intentionally not enabled by default in the example Compose.
auto is conservative: if a selected hardware encoder fails to launch, that render is retried once with libx264. It does not silently loop through six encoders.
Core runtime variables:
| Variable | Default | Purpose |
|---|---|---|
AUTOSUBS_PORT |
3000 |
HTTP port inside the container. |
AUTOSUBS_CONFIG_DIR |
/config |
Local SQLite/config directory. |
AUTOSUBS_DATA_DIR |
/data |
Upload/job/render working data. |
AUTOSUBS_FONTS_DIR |
/fonts |
Custom font directory. |
AUTOSUBS_ALLOWED_ROOTS |
/data:/media in the image |
Colon-separated roots exposed by the server picker/workflows. If omitted outside Docker, AutoSubs falls back to its data directory. |
AUTOSUBS_MAX_RENDER_JOBS |
2 |
Concurrent expensive render slots. |
AUTOSUBS_MAX_TRANSCRIPTION_JOBS |
2 |
Concurrent transcription slots. |
AUTOSUBS_MAX_QUEUED_JOBS |
256 |
Maximum number of active queued/processing jobs admitted at once. |
AUTOSUBS_WORKFLOW_SCAN_SECONDS |
5 |
Periodic workflow reconciliation interval (also covers NFS writes missed by native events). |
AUTOSUBS_FILE_STABILITY_MS |
2000 |
Required stable-size/mtime window before a watched file is accepted. |
AUTOSUBS_MAX_UPLOAD_BYTES |
53687091200 |
Maximum resumable upload size (50 GiB). |
First-database bootstrap variables:
| Variable | Purpose |
|---|---|
AUTOSUBS_TRANSCRIPTION_LANGUAGE |
Initial transcription language. |
AUTOSUBS_TRANSCRIPTION_URL |
Initial external transcription endpoint. |
AUTOSUBS_TRANSCRIPTION_MODEL |
Initial external model. |
AUTOSUBS_LOCAL_TRANSCRIPTION_ENABLED |
Enable the local provider initially. |
AUTOSUBS_LOCAL_TRANSCRIPTION_URL |
Initial Speaches/local endpoint. |
AUTOSUBS_LOCAL_TRANSCRIPTION_MODEL |
Initial local model. |
AUTOSUBS_LOCAL_FALLBACK_ENABLED |
Allow local fallback after external failure. |
AUTOSUBS_TRANSCRIPTION_API_KEY |
Optional initial external-provider key. |
AUTOSUBS_LOCAL_TRANSCRIPTION_API_KEY |
Optional initial local-provider key. |
Provider keys can be bootstrapped through environment variables or configured later in Settings. Stored secrets are never echoed back to the browser.
The current API is versioned under /api/v1:
GET /api/v1/health
GET /api/v1/capabilities
GET /api/v1/events SSE
GET/POST /api/v1/jobs
GET/PUT /api/v1/jobs/{id}
GET /api/v1/jobs/{id}/media Range-aware video stream
POST /api/v1/jobs/{id}/cancel
POST /api/v1/jobs/{id}/render
PUT/DELETE /api/v1/jobs/{id}/sidecar
GET/PUT /api/v1/jobs/{id}/subtitles
POST /api/v1/jobs/{id}/subtitles/regroup
POST /api/v1/jobs/{id}/subtitles/shift
GET /api/v1/jobs/{id}/subtitles/export
POST /api/v1/uploads tus create
HEAD/PATCH /api/v1/uploads/{id} tus resume/upload
DELETE /api/v1/uploads/{id}
GET/POST /api/v1/presets
GET/PUT/DEL /api/v1/presets/{id}
GET/POST /api/v1/brands
GET/PUT/DEL /api/v1/brands/{id}
GET/POST /api/v1/workflows
GET/PUT/DEL /api/v1/workflows/{id}
GET/PUT /api/v1/settings
GET /api/v1/files/roots
GET /api/v1/files/browse
GET/POST /api/v1/assets
DELETE /api/v1/assets/{id}
The server-side picker canonicalizes requested paths after symlink resolution and rejects paths outside AUTOSUBS_ALLOWED_ROOTS.
The release build uses Node only as a frontend build stage. The runtime image contains no Node.js toolchain.
# Rust
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features
# Frontend
cd frontend
npm ci
npm run check
npm test
npm run build
# Full image
docker build -t autosubs:dev .main is gated by CI for Rust, SvelteKit and Docker. Dependency updates cover Cargo, npm, Docker and GitHub Actions. Tagged releases publish multi-architecture GHCR images with SBOM/provenance metadata.
Contributions are welcome — see .github/CONTRIBUTING.md. For usage help, see .github/SUPPORT.md.
src/api/ HTTP/SSE/tus endpoints
src/jobs.rs persistent queue + job runner
src/media/ ffprobe, transcription, FFmpeg plans and rendering
src/subtitle/ SRT/ASS, timing normalization, segmentation, LLM correction
src/workflows.rs watcher supervisor + periodic reconciliation
frontend/ SvelteKit static client + frontend tests
docs/ docs and versioned UI previews
Rust regression tests live next to the modules they exercise under `#[cfg(test)]`.
AutoSubs can read and write mounted media paths and can invoke FFmpeg on them. Do not expose it directly to the public Internet. Put it behind your normal authenticated reverse proxy/VPN and mount only the directories it actually needs.
See security policy for vulnerability reporting and deployment notes.
MIT — see LICENSE.