Self-hosted AI-powered transcription platform with speaker diarization, search, and collaboration features. Built with Svelte, FastAPI, and Docker for easy deployment.
95
stars
2,476
commits
Python
primary language
Sep 9, 2026
updated
AI-Powered Transcription and Media Analysis Platform
Project status — active development. The default branch tracks ongoing work and may contain unreleased or in-progress features. For a stable deployment, install a published release — the one-line installer below resolves the latest release automatically and pins your deployment to it.
OpenTranscribe is a powerful, containerized web application for transcribing and analyzing audio/video files using state-of-the-art AI models. Built with modern technologies and designed for scalability, it provides an end-to-end solution for speech-to-text conversion, speaker identification, and content analysis.
Note: This application is 99.9% written by AI using frontier models from commercial providers, demonstrating the power of AI-assisted development.
Complete workflow: Login → Upload → Process → Transcribe → Speaker Identification → AI Tags & Collections
📚 For detailed screenshots and visual guides, see the Complete Documentation
🗺️ Where this is going: the Roadmap shows what is in each release and how far along it is, generated from the issue tracker. Release Themes explains what each version is for and the exit criteria it has to meet.
meeting_P001.mp4, meeting_P002.mp4) from dropped connections and rejoins them into one file with ffmpeg before transcriptionQ3 Review = q3-review), so one word never becomes three near-duplicatesGET /usage/me shows tokens and estimated cost per model, so you can see what you are spending./opentr.sh start dev --with-mock-llm runs an OpenAI-compatible mock so chat works with no GPU, API key, or internet — including scenario models that exercise the real error pathsLLM_PROVIDER empty and transcription, diarization, cross-recording speaker matching, redaction, tags, collections, exports, watch sources and analytics all work normallyDEPLOYMENT_MODE=lite for cloud-ASR-only deployments without requiring a local GPU/scim/v2 for IdP-driven account creation/deactivation, alongside per-method JIT provisioning[CATEGORY] placeholders — the full original transcript is always kept in the database, masking is a read-time transform (no destructive edits)celery-redaction CPU service; spans cache on the transcript so enable/disable and category changes are instant--with-gpu-split) that runs transcription and diarization on separate GPUs--lite image, not MPS)ENABLE_BENCHMARK_TIMING) with admin timing endpoints--blackwell flagSTORAGE_BACKEND=s3 targets a real S3 (or S3-compatible) endpoint directly, with SigV4 signing and IAM-role credentials (no static keys required) for AWS-native deployments# Required
- Docker and Docker Compose
- 8GB+ RAM (16GB+ recommended)
# Recommended for optimal performance
- NVIDIA GPU with CUDA support
Run this one-liner to download and set up OpenTranscribe using our pre-built Docker Hub images:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
Then follow the on-screen instructions. The setup script will:
opentranscribe.sh)💻 CPU-only install: If you don't have an NVIDIA GPU, or you're on WSL2 with the NVIDIA Container Toolkit installed but GPU passthrough disabled, pass --cpu to skip GPU detection and avoid the nvidia-container-cli adapter error at container start:
# Piped install
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash -s -- --cpu
# Unattended / CI equivalent
OPENTRANSCRIBE_FORCE_CPU=1 curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
The CPU-only choice is persisted to .env as FORCE_CPU_MODE=true so subsequent ./opentranscribe.sh start/restart calls continue to skip the GPU overlay automatically.
🪶 Lite install (--lite): --cpu still runs the full CUDA image, just without a GPU. --lite
is different — it installs the much smaller CPU-only opentranscribe-backend-lite image, which
carries no CUDA runtime and no local ASR model, and transcribes via a cloud ASR provider you
configure after install. Implies --cpu, and persists DEPLOYMENT_MODE=lite:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash -s -- --lite
# Unattended / CI equivalent
OPENTRANSCRIBE_LITE=1 curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
This is the only supported shape on a host with no NVIDIA GPU at all, and it is what arm64 hosts select automatically — the full CUDA image publishes no arm64 leg, so on Apple Silicon and other aarch64 machines the lite image is the only backend available.
⚠️ IMPORTANT - HuggingFace Setup: The script will prompt you for your HuggingFace token during setup. BEFORE running the installer:
If you provide a valid token with the model agreement accepted, AI models will be downloaded and cached before Docker starts, ensuring the app is ready to use immediately. If you skip this step, models will download on first use (10-30 minute delay).
Once setup is complete, start OpenTranscribe with:
cd opentranscribe
./opentranscribe.sh start
The Docker images are available on Docker Hub as separate repositories:
davidamacey/opentranscribe-backend: Backend service (also used for celery-worker and flower)davidamacey/opentranscribe-frontend: Frontend serviceAccess the web interface at http://localhost:5173
Clone the Repository
git clone https://github.com/attevon-llc/OpenTranscribe.git
cd OpenTranscribe
# Make utility script executable
chmod +x opentr.sh
Environment Configuration
# Copy environment template
cp .env.example .env
# Edit .env file with your settings (optional for development)
# Key variables:
# - HUGGINGFACE_TOKEN (required for speaker diarization)
# - GPU settings for optimal performance
Start OpenTranscribe
# Start in development mode (with hot reload)
./opentr.sh start dev
# Or start in production mode
./opentr.sh start prod
Access the Application
--with-monitoring)--with-monitoring)The opentr.sh script provides comprehensive management for all application operations:
# Start the application
./opentr.sh start [dev|prod] # Start in development or production mode
./opentr.sh start dev --gpu-scale # Start with multi-GPU scaling (optional)
./opentr.sh stop # Stop all services
./opentr.sh status # Show container status
./opentr.sh logs [service] # View logs (all or specific service)
Two independent scaling modes are available — choose based on your hardware and workload:
Option A — GPU Scale (multiple parallel pipelines on one GPU):
# The --gpu-scale flag is what enables scaling — GPU_SCALE_ENABLED in .env does
# NOT turn it on (it only affects which GPU the system-stats display queries).
GPU_SCALE_DEVICE_ID=2 # Which GPU to use (default: 2)
GPU_SCALE_WORKERS=4 # Number of parallel workers (default: 4)
# Start with GPU scaling
./opentr.sh start dev --gpu-scale
./opentr.sh reset dev --gpu-scale
# Example: GPU 2 (A6000) runs 4 parallel workers; GPU 0/1 handle other tasks
Best for: High file throughput — processes 4 videos simultaneously on one GPU.
Option B — GPU Split (transcription and diarization on separate GPUs):
# Configure in .env
GPU_TRANSCRIBE_DEVICE_ID=0 # GPU for WhisperX (transcription)
GPU_DIARIZE_DEVICE_ID=1 # GPU for PyAnnote (diarization)
ENGINE_SHARED_VOLUME_PATH=/scratch/opentranscribe/engine # per-task handoff dir on the pipeline_scratch volume
# Start with GPU split
./opentr.sh start dev --with-gpu-split
./opentr.sh reset dev --with-gpu-split
Best for: Two-GPU setups where you want dedicated VRAM per model — one GPU purely for Whisper, one purely for PyAnnote.
📖 Deployment reference: For a full table of every deployment type and its exact
./opentr.shcommand — plus the first-init healthcheck model, the cross-worker scratch-volume contract, all three GPU modes, the security posture (loopback infra ports,no-new-privileges, secret generation), and the NAS/NVMe storage overlay — see the Deployment Configuration operations guide.
# Mount a host folder to watch for new media (the only watch env var),
# then start with the watch overlay:
WATCH_HOST_PATH=/path/to/your/media ./opentr.sh start dev --with-watch
# Optional: a local Samba share to test an SMB watch source
./opentr.sh start dev --with-watch --with-smb-test
# Seed sample media (multi-part group, duplicate, old file, mixed types)
bash scripts/setup-watch-source-test-data.sh ./watch
Then configure sources in Settings → Watch Sources (local folder, S3, or SMB). Without --with-watch, the local-folder type is hidden and only S3/SMB are available. All connection, schedule, and credential settings are managed in the UI — no restart required.
# Brand-new isolated stack: own compose project + named volumes, NAS overlay
# NEVER loaded, real data untouched. Runs on the standard dev ports by default
# (refuses to start if the main stack already holds them).
./opentr.sh start dev --fresh test1
# Run side-by-side with the main stack by offsetting every published port:
./opentr.sh start dev --fresh test1 --port-offset 100 # backend :5274, frontend :5273, ...
# Upload a couple of small sample files once the stack is healthy:
./opentr.sh start dev --fresh test1 --seed-benchmark
# Manage fresh deployments:
./opentr.sh stop --fresh test1 # stop (keep volumes)
./opentr.sh status --fresh test1 # status
./opentr.sh fresh-list # list all fresh deployments + volumes
./opentr.sh fresh-destroy test1 # remove containers + volumes (confirmed)
# See exactly where your live data lives before deleting anything:
./opentr.sh data-paths
Fresh deployments are the safe way to spin up throwaway stacks. They use an
isolated otfresh-<name> compose project (separate containers and named
volumes), and the NAS/bind-mount overlay is never attached — so the production
dataset can never be touched. The non-fresh start auto-loads the NAS overlay
when storage paths are set in .env (with a prominent banner); pass --no-nas
to suppress it. Add --dry-run to any start to print the exact compose files
and command without launching anything.
# Start the optional observability stack alongside the app
./opentr.sh start dev --with-monitoring
Prometheus scrapes the backend's /metrics endpoint; Grafana (:5185, default login admin / $GRAFANA_PASSWORD) ships with pre-provisioned ops and product dashboards. The overlay is fully optional — omit the flag and the stack runs unchanged. See Monitoring & Logging for the dashboard tour, JSON access-log analysis, and AWS notes.
# Mount a backup destination, then configure schedule/destination in the admin UI
./opentr.sh start dev --with-backup
Built-in scheduled database backups run on the existing celery-beat service — no host cron. Configure everything in Settings → System Management → Backups: cron schedule, GFS retention, optional gpg encryption, and a destination that is either a mounted folder or an S3-compatible bucket (AWS S3 / MinIO / Backblaze — keeps backups off the host machine). See Backup & Restore.
If the database is ever lost but the MinIO media survives, Storage Recovery rebuilds the catalog in place (python -m app.scripts.reingest_minio) — no re-download, no duplication.
# Service management
./opentr.sh restart-backend # Restart API and workers without database reset
./opentr.sh restart-frontend # Restart frontend only
./opentr.sh restart-all # Restart all services without data loss
# Container rebuilding (after code changes)
./opentr.sh rebuild-backend # Rebuild backend with new code
./opentr.sh rebuild-frontend # Rebuild frontend with new code
./opentr.sh build # Rebuild all containers
# Data operations (⚠️ DESTRUCTIVE)
./opentr.sh reset [dev|prod] # Complete reset - deletes ALL data!
# Alembic migrations run automatically on dev backend startup — no separate init command needed.
# Backup and restore
./opentr.sh backup # Create timestamped database backup
./opentr.sh backup --encrypt # GPG-encrypted backup (AES-256, no plaintext on disk)
./opentr.sh restore [--yes] [--no-safety-dump] [--from-s3] <file> # REPLACE the database from a backup
# (.sql, .dump, .sql.gpg, .dump.gpg; --from-s3 fetches by name first) — destructive
# Production installs (no repo clone, no opentr.sh) use the identical commands via the
# shipped management script instead: ./opentranscribe.sh backup / restore — same flags,
# same behavior. See docs-site/docs/operations/backup-restore.md.
# Maintenance
./opentr.sh health # Check service health status
./opentr.sh shell [service] # Open shell in container
# Available services: backend, frontend, postgres, redis, minio, opensearch, celery-worker
# View specific service logs
./opentr.sh logs backend # API server logs
./opentr.sh logs celery-worker # AI processing logs
./opentr.sh logs frontend # Frontend development logs
./opentr.sh logs postgres # Database logs
# Follow logs in real-time
./opentr.sh logs backend -f
User Registration
Upload or Record Content
Monitor Processing
Explore Your Content
Configure AI Features (Optional)
🎙️ Device Selection → 📊 Level Monitoring → ⏸️ Session Control → ⬆️ Background Upload
🤖 LLM Configuration → 📝 Custom Prompts → 🔍 Content Analysis → 📊 BLUF Summaries
👥 Automatic Detection → 🤖 AI Recognition → 🏷️ Profile Management → 🔍 Cross-Media Tracking
⬆️ Concurrent Uploads → 📊 Progress Tracking → 🔄 Retry Logic → 📋 Queue Management
🔍 Keyword Search → 🧠 Semantic Search → 🏷️ Smart Filtering → 🎯 Waveform Navigation
📁 Create Collections → 📂 Organize Files → 🏷️ Bulk Operations → 🎯 Inline Editing
🔔 Progress Updates → 📊 Status Tracking → 🔄 WebSocket Integration → ✅ Completion Alerts
📄 Multiple Formats → 📺 Subtitle Files → 🔗 API Access → 🎬 Media Downloads
OpenTranscribe/
├── 📁 backend/ # Python FastAPI backend
│ ├── 📁 app/ # Application modules
│ │ ├── 📁 api/ # REST API endpoints
│ │ ├── 📁 models/ # Database models
│ │ ├── 📁 services/ # Business logic
│ │ ├── 📁 tasks/ # Background AI processing
│ │ ├── 📁 utils/ # Common utilities
│ │ └── 📁 db/ # Database configuration
│ ├── 📁 scripts/ # Admin and maintenance scripts
│ ├── 📁 tests/ # Comprehensive test suite
│ └── 📄 README.md # Backend documentation
├── 📁 frontend/ # Svelte frontend application
│ ├── 📁 src/ # Source code
│ │ ├── 📁 components/ # Reusable UI components
│ │ ├── 📁 routes/ # Page components
│ │ ├── 📁 stores/ # State management
│ │ └── 📁 styles/ # CSS and themes
│ └── 📄 README.md # Frontend documentation
├── 📁 database/ # Database initialization
├── 📁 models_ai/ # AI model storage (runtime)
├── 📁 scripts/ # Utility scripts
├── 📄 docker-compose.yml # Container orchestration
├── 📄 opentr.sh # Main utility script
└── 📄 README.md # This file
# Database
DATABASE_URL=postgresql://postgres:password@postgres:5432/opentranscribe
# Security
SECRET_KEY=your-super-secret-key-here
JWT_SECRET_KEY=your-jwt-secret-key
# Object Storage
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_BUCKET_NAME=transcribe-app
There is no single AUTH_TYPE switch — every method is enabled independently, and all of them
can run at once (each account records which one owns it). These .env values are only a
bootstrap seed / fallback: Settings → Authentication in the admin UI is DB-backed and takes
precedence over .env, with no restart required.
# LDAP/Active Directory
LDAP_ENABLED=false
LDAP_SERVER=ldap://your-ldap-server:389
LDAP_BASE_DN=dc=example,dc=com
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_PASSWORD=your-bind-password
# OpenID Connect (any conforming provider, including Keycloak — the surface used to
# be Keycloak-specific; the legacy KEYCLOAK_* names still work as a permanent alias
# for OIDC_*, and win if both are set)
OIDC_ENABLED=false
OIDC_SERVER_URL=https://your-idp-server
OIDC_REALM=your-realm
OIDC_CLIENT_ID=opentranscribe
OIDC_CLIENT_SECRET=your-client-secret
# SAML 2.0
SAML_ENABLED=false
# PKI/X.509
PKI_ENABLED=false
PKI_CA_CERT_PATH=/path/to/ca-cert.pem
PKI_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 # required whenever PKI is enabled
# Trusted-header (reverse proxy)
PROXY_ENABLED=false
PROXY_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 # required whenever proxy auth is enabled
# MFA (optional, works with any auth type)
MFA_ENABLED=false
MFA_ISSUER=OpenTranscribe
See detailed setup guides: LDAP | OIDC | PKI | SAML | Trusted-header proxy
# Required for speaker diarization - see setup instructions below
HUGGINGFACE_TOKEN=your_huggingface_token_here
# Model configuration
WHISPER_MODEL=large-v3-turbo # large-v3-turbo (default), large-v3, large-v2, medium, small, base
COMPUTE_TYPE=float16 # float16, int8
BATCH_SIZE=16 # Reduce if GPU memory limited
# Speaker detection
MIN_SPEAKERS=1 # Minimum speakers to detect
MAX_SPEAKERS=20 # Maximum speakers to detect (can be increased to 50+ for large conferences)
# Model caching (recommended)
MODEL_CACHE_DIR=./models # Directory to store downloaded AI models
OpenTranscribe offers flexible AI deployment options. Choose the approach that best fits your infrastructure:
🔧 Quick Setup Options:
Cloud-Only (Recommended for Most Users)
# Configure for OpenAI in .env
LLM_PROVIDER=openai
OPENAI_API_KEY=your_openai_key
OPENAI_MODEL_NAME=gpt-4o-mini
# Start without local LLM
./opentr.sh start dev
Local vLLM (Self-Hosted)
# Deploy vLLM server separately, then configure in .env
LLM_PROVIDER=vllm
VLLM_BASE_URL=http://your-vllm-server:8000/v1
VLLM_MODEL_NAME=gpt-oss-20b
# Start OpenTranscribe
./opentr.sh start dev
Local Ollama (Self-Hosted)
# Deploy Ollama server separately, then configure in .env
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://your-ollama-server:11434
OLLAMA_MODEL_NAME=llama3.2:3b-instruct-q4_K_M
# Start OpenTranscribe
./opentr.sh start dev
📋 Complete Provider Configuration:
# Cloud Providers (configure in .env)
LLM_PROVIDER=openai # openai, anthropic, custom (openrouter)
OPENAI_API_KEY=your_openai_key # OpenAI GPT models
ANTHROPIC_API_KEY=your_claude_key # Anthropic Claude models
OPENROUTER_API_KEY=your_or_key # OpenRouter (multi-provider)
# Local Providers (requires additional Docker services)
LLM_PROVIDER=vllm # Local vLLM server
LLM_PROVIDER=ollama # Local Ollama server
🎯 Deployment Scenarios:
LLM_PROVIDER empty. Transcription, diarization, redaction and full hybrid search (keyword + semantic) all still work — only summaries, topic suggestions, speaker-ID hints and AI Chat need a providerSee LLM Integration for detailed setup instructions.
OpenTranscribe automatically downloads and caches AI models for optimal performance. Models are saved locally and reused across container restarts.
Default Setup:
./models/ directory in your project folderDirectory Structure:
./models/
├── huggingface/ # PyAnnote + WhisperX models
│ ├── hub/ # WhisperX transcription models (~1.5GB)
│ └── transformers/ # PyAnnote transformer models
└── torch/ # PyTorch cache
└── pyannote/ # PyAnnote diarization models (~500MB)
Custom Cache Location:
# Set custom directory in your .env file
MODEL_CACHE_DIR=/path/to/your/models
# Examples:
MODEL_CACHE_DIR=~/ai-models # Home directory
MODEL_CACHE_DIR=/mnt/storage/models # Network storage
MODEL_CACHE_DIR=./cache # Project subdirectory
Storage Requirements:
OpenTranscribe requires a HuggingFace token for speaker diarization and voice fingerprinting features. Follow these steps:
You MUST accept the user agreement for the PyAnnote diarization model or speaker diarization will fail:
⚠️ Common Issue: If the agreement isn't accepted, downloads will fail with
'NoneType' object has no attribute 'eval'or an HTTP 403/PermissionError. Older docs mentionedpyannote/segmentation-3.0andpyannote/speaker-diarization-3.1— that pair is optional and only helps the in-process PyAnnote engine's internal last-resort fallback; it is never a substitute for acceptingcommunity-1.
Add your token to the environment configuration:
For Production Installation:
# The setup script will prompt you for your token
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
For Manual Installation:
# Add to .env file
echo "HUGGINGFACE_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" >> .env
Note: Without a valid HuggingFace token, speaker diarization will be disabled and speakers will not be automatically detected or identified across different media files.
# GPU settings
USE_GPU=true # Enable GPU acceleration
CUDA_VISIBLE_DEVICES=0 # GPU device selection
# Resource limits
MAX_UPLOAD_SIZE=4GB # Maximum file size (supports GoPro videos)
CELERY_WORKER_CONCURRENCY=2 # Concurrent tasks
For production use, ensure you:
Security Configuration
# Generate strong secrets
openssl rand -hex 32 # For SECRET_KEY
openssl rand -hex 32 # For JWT_SECRET_KEY
# Set strong database passwords
# Configure proper firewall rules
# Set up SSL/TLS certificates
Performance Optimization
# Use production environment
NODE_ENV=production
# Configure resource limits
# Set up monitoring and logging
# Configure backup strategies
HTTPS/SSL Setup (Required for microphone recording from other devices)
OpenTranscribe includes built-in NGINX reverse proxy support with SSL/TLS:
# Quick setup for homelab/local network
./scripts/generate-ssl-cert.sh opentranscribe.local --auto-ip
# Add to .env
NGINX_SERVER_NAME=opentranscribe.local
# Start with HTTPS enabled
./opentr.sh start dev
For detailed instructions including Let's Encrypt setup, see docs/NGINX_SETUP.md.
Note: Modern browsers require HTTPS for microphone access. Without NGINX/SSL setup, microphone recording will only work when accessing via
localhost.
# Start development with hot reload
./opentr.sh start dev
# Backend development
cd backend/
pip install -r requirements.txt
pytest tests/ # Run tests
ruff format app/ # Format code
ruff check app/ # Lint code
# Frontend development
cd frontend/
npm install
npm run dev # Development server
npm run test # Run tests
npm run lint # Lint code
Releases run through one script — don't hand-run git tag, docker push, or
gh release:
./scripts/release.sh status # where am I?
./scripts/release.sh reset 0.5.0 # clear rehearsal history before a real run
./scripts/release.sh preflight 0.5.0 # seconds — fails fast on the usual suspects
./scripts/release.sh run 0.5.0 # the whole sequence
./scripts/release.sh run 0.5.0 --dry-run # print every command, execute nothing
Twelve stages, each independently runnable, skippable (--skip) and resumable
(--from):
preflight → bump → verify → test → build → scan → rehearse
→ tag → publish → smoke → promote → finish
The last four are the only ones that reach Docker Hub or GitHub, and each needs
an explicit --yes. Before they run, two rehearsal scenarios prove the release
end to end on real data: a fresh install via the documented one-liner, and an
in-place upgrade from the previous published release — including a file
uploaded after the upgrade, to prove the upgraded stack still does its job.
📖 Full guide: Developer Guide → Releasing
Testing is local-first: GitHub Actions runs the unit/API suite as a safety net, but the complete suite (S3/OpenSearch integration, browser E2E) needs the live dev stack and runs locally.
# The pre-merge gate — runs EVERYTHING against the live stack
# (ungated suite, security-gated suites in both FIPS modes, integration tests)
./scripts/run-integration-tests.sh # add --coverage / --e2e-smoke
# Backend tests (host venv; MinIO/OpenSearch tests auto-enable when the stack is up)
source backend/venv/bin/activate
cd backend/
pytest tests/ # All tests
pytest tests/api/ # API tests only
pytest --cov=app tests/ # With coverage (report-only, no threshold yet)
# Frontend tests
cd frontend/
npm run test # Vitest unit + component tests (jsdom)
npm run test:coverage # …with coverage
npm run check # svelte-check (types + a11y)
npm run lint # ESLint (flat config)
npm run check:i18n # locale key-parity across all 8 languages
# Browser end-to-end (Playwright via pytest, against the live stack)
./scripts/e2e/run-e2e.sh # full e2e suite, headless
./scripts/e2e/run-e2e-smoke.sh # quick read-mostly subset
./scripts/e2e/run-e2e.sh -m upload # one marker: upload/search/settings/
# transcription/gallery/auth/visual
pytest backend/tests/e2e/test_a11y.py -v # axe-core accessibility
pytest backend/tests/e2e/test_visual_regression.py -v # screenshot baselines
Tools that keep the suite honest. A test that cannot fail is worse than no test — it buys false confidence and hides the defect it was written to catch. These four exist because this repo had shipped every one of those failure modes: an assertion that passed against an empty index, a marker that selected no tests, 240 security tests gated off behind stale environment variables, and an endpoint returning a hardcoded value that no test referenced.
python3 scripts/audit-tests.py backend/tests # 16 AST detectors, exits 1 on new offenders
cd frontend && npm run test:audit # the vitest sibling, 10 detectors
npm run test:audit:selftest # ...and ITS self-test — not optional
python3 scripts/analyze-test-timing.py <junit.xml> [--baseline baseline.xml]
./scripts/run-mutation-tests.sh --module spans # opt-in; never in the gate or CI
file::test::category —
keyed by test alone, one entry once exempted a test from every detector at once.analyze-test-timing.py finds barriers, not just slow tests. Unrelated tests from
many files sharing a sub-second duration band is a released lock queue, not a
coincidence — that is how one worker was found owning 81% of the wall clock.python -m cProfile -o out.prof -m pytest <test> settled in one pass what two plausible hypotheses had cost two full measurement
cycles.Current (measured 2026-08-13): backend 6,623 passed / 62 real skips / 104 s (from
4,752 / 458 / 511 s); frontend 669 passed / 76 files / 21.6 s; e2e 341 collected.
A residual ~9 s DDL cluster remains (the ddl_exclusive advisory-lock queue); the
sub-second barriers are gone. Re-derive rather than trust these — the values printed
here previously were wrong by 1,294 backend and 188 frontend tests;
./scripts/run-backend-tests.sh --summary answers in seconds.
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
# Check GPU availability
nvidia-smi
# Verify Docker GPU support
docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
# Set CPU-only mode if needed
echo "USE_GPU=false" >> .env
Symptoms:
Permission denied: '/home/appuser/.cache/huggingface/hub'Permission denied: '/home/appuser/.cache/yt-dlp'Cause: Docker creates model cache directories with root ownership, but containers run as non-root user (UID 1000) for security.
Solution:
# Option 1: Run the automated permission fix script (recommended)
cd opentranscribe # Or your installation directory
./scripts/fix-model-permissions.sh
# Option 2: Manual fix using Docker
docker run --rm -v ./models:/models busybox chown -R 1000:1000 /models
# Option 3: Manual fix using sudo (if available)
sudo chown -R 1000:1000 ./models
sudo chmod -R 755 ./models
Prevention for New Installations:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
Why This Happens:
Verification:
# Check directory ownership (should show UID 1000 or your user)
ls -la models/
# Test write permissions
touch models/huggingface/test.txt && rm models/huggingface/test.txt
# Reduce model size
echo "WHISPER_MODEL=medium" >> .env
echo "BATCH_SIZE=8" >> .env
echo "COMPUTE_TYPE=int8" >> .env
# Monitor memory usage
docker stats
USE_GPU=true)WHISPER_MODEL=medium)# Reset database
./opentr.sh reset dev
# Check database logs
./opentr.sh logs postgres
# Verify database is running
./opentr.sh shell postgres psql -U postgres -l
# Check service status
./opentr.sh status
# Full reset (⚠️ deletes all data)
./opentr.sh reset dev
For systems where the GPU cannot fit the full transcription model, OpenTranscribe automatically activates hybrid mode: transcription runs on CPU while diarization stays on GPU. This requires only ~1.3 GB VRAM for PyAnnote and delivers speaker-diarized transcripts without a dedicated GPU. This is a Linux/WSL2-with-NVIDIA-GPU feature — there is no GPU/MPS path available on macOS (Docker Desktop has no Metal passthrough), so macOS deployments use the --lite (CPU-only) image instead and run both stages on CPU.
| Scenario | Transcription | Diarization | Trigger |
|---|---|---|---|
| GPU ≥ 8 GB + large-v3-turbo | GPU | GPU | Normal mode |
| GPU 4–6 GB + large-v3-turbo | CPU (small model) | GPU | Auto hybrid |
| macOS (any Apple Silicon) | CPU (small model) | CPU (--lite image) | Always CPU-only — no MPS in Docker |
WHISPER_HYBRID_MODE=true | CPU (small model) | GPU | Manual override (Linux/WSL2 + NVIDIA GPU only) |
The CPU model defaults to small (int8, ~15–30× real-time on modern hardware). Override with WHISPER_HYBRID_CPU_MODEL=medium for better accuracy at the cost of speed.
# Force hybrid mode on (useful for testing or shared-GPU deployments)
WHISPER_HYBRID_MODE=true
WHISPER_HYBRID_CPU_MODEL=small # small | medium | base
# Force hybrid mode off (never auto-activate)
WHISPER_HYBRID_MODE=false
# Auto-detect (default — recommended)
WHISPER_HYBRID_MODE=auto
# GPU optimization (≥ 8 GB VRAM)
COMPUTE_TYPE=float16 # Use half precision
BATCH_SIZE=auto # Auto-tuned per model (turbo→16, medium→24, small→24)
WHISPER_MODEL=large-v3-turbo # Default: fast and accurate; use large-v3 for translation or max accuracy
# Hybrid mode (low-VRAM GPU or macOS — CPU transcription + GPU diarization)
WHISPER_HYBRID_MODE=auto # Auto-activates when GPU VRAM is insufficient; always on for macOS
WHISPER_HYBRID_CPU_MODEL=small # Transcription model used in hybrid mode (small | medium | base)
# CPU-only (no GPU)
WHISPER_HYBRID_MODE=true # Force CPU transcription
WHISPER_HYBRID_CPU_MODEL=small # small (good accuracy) or base (faster, lower accuracy)
OpenTranscribe supports multiple authentication methods for enterprise and government deployments:
/scim/v2 (RFC 7643/7644)For government deployments, OpenTranscribe includes features aligned with FedRAMP controls:
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) - see the LICENSE file for details.
The AGPL-3.0 license ensures that:
Built with ❤️ using AI assistance and modern open-source technologies.
OpenTranscribe demonstrates the power of AI-assisted development while maintaining full local control over your data and processing.
2,322 commits
123 commits
16 commits
12 commits
Python
58.7%
HTML
18.9%
Svelte
9.0%
Shell
5.4%
TypeScript
5.0%
C
2.2%
Self-hosted AI-powered transcription platform with speaker diarization, search, and collaboration features. Built with Svelte, FastAPI, and Docker for easy deployment.
95
stars
2,476
commits
Python
primary language
Sep 9, 2026
updated
AI-Powered Transcription and Media Analysis Platform
Project status — active development. The default branch tracks ongoing work and may contain unreleased or in-progress features. For a stable deployment, install a published release — the one-line installer below resolves the latest release automatically and pins your deployment to it.
OpenTranscribe is a powerful, containerized web application for transcribing and analyzing audio/video files using state-of-the-art AI models. Built with modern technologies and designed for scalability, it provides an end-to-end solution for speech-to-text conversion, speaker identification, and content analysis.
Note: This application is 99.9% written by AI using frontier models from commercial providers, demonstrating the power of AI-assisted development.
Complete workflow: Login → Upload → Process → Transcribe → Speaker Identification → AI Tags & Collections
📚 For detailed screenshots and visual guides, see the Complete Documentation
🗺️ Where this is going: the Roadmap shows what is in each release and how far along it is, generated from the issue tracker. Release Themes explains what each version is for and the exit criteria it has to meet.
meeting_P001.mp4, meeting_P002.mp4) from dropped connections and rejoins them into one file with ffmpeg before transcriptionQ3 Review = q3-review), so one word never becomes three near-duplicatesGET /usage/me shows tokens and estimated cost per model, so you can see what you are spending./opentr.sh start dev --with-mock-llm runs an OpenAI-compatible mock so chat works with no GPU, API key, or internet — including scenario models that exercise the real error pathsLLM_PROVIDER empty and transcription, diarization, cross-recording speaker matching, redaction, tags, collections, exports, watch sources and analytics all work normallyDEPLOYMENT_MODE=lite for cloud-ASR-only deployments without requiring a local GPU/scim/v2 for IdP-driven account creation/deactivation, alongside per-method JIT provisioning[CATEGORY] placeholders — the full original transcript is always kept in the database, masking is a read-time transform (no destructive edits)celery-redaction CPU service; spans cache on the transcript so enable/disable and category changes are instant--with-gpu-split) that runs transcription and diarization on separate GPUs--lite image, not MPS)ENABLE_BENCHMARK_TIMING) with admin timing endpoints--blackwell flagSTORAGE_BACKEND=s3 targets a real S3 (or S3-compatible) endpoint directly, with SigV4 signing and IAM-role credentials (no static keys required) for AWS-native deployments# Required
- Docker and Docker Compose
- 8GB+ RAM (16GB+ recommended)
# Recommended for optimal performance
- NVIDIA GPU with CUDA support
Run this one-liner to download and set up OpenTranscribe using our pre-built Docker Hub images:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
Then follow the on-screen instructions. The setup script will:
opentranscribe.sh)💻 CPU-only install: If you don't have an NVIDIA GPU, or you're on WSL2 with the NVIDIA Container Toolkit installed but GPU passthrough disabled, pass --cpu to skip GPU detection and avoid the nvidia-container-cli adapter error at container start:
# Piped install
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash -s -- --cpu
# Unattended / CI equivalent
OPENTRANSCRIBE_FORCE_CPU=1 curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
The CPU-only choice is persisted to .env as FORCE_CPU_MODE=true so subsequent ./opentranscribe.sh start/restart calls continue to skip the GPU overlay automatically.
🪶 Lite install (--lite): --cpu still runs the full CUDA image, just without a GPU. --lite
is different — it installs the much smaller CPU-only opentranscribe-backend-lite image, which
carries no CUDA runtime and no local ASR model, and transcribes via a cloud ASR provider you
configure after install. Implies --cpu, and persists DEPLOYMENT_MODE=lite:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash -s -- --lite
# Unattended / CI equivalent
OPENTRANSCRIBE_LITE=1 curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
This is the only supported shape on a host with no NVIDIA GPU at all, and it is what arm64 hosts select automatically — the full CUDA image publishes no arm64 leg, so on Apple Silicon and other aarch64 machines the lite image is the only backend available.
⚠️ IMPORTANT - HuggingFace Setup: The script will prompt you for your HuggingFace token during setup. BEFORE running the installer:
If you provide a valid token with the model agreement accepted, AI models will be downloaded and cached before Docker starts, ensuring the app is ready to use immediately. If you skip this step, models will download on first use (10-30 minute delay).
Once setup is complete, start OpenTranscribe with:
cd opentranscribe
./opentranscribe.sh start
The Docker images are available on Docker Hub as separate repositories:
davidamacey/opentranscribe-backend: Backend service (also used for celery-worker and flower)davidamacey/opentranscribe-frontend: Frontend serviceAccess the web interface at http://localhost:5173
Clone the Repository
git clone https://github.com/attevon-llc/OpenTranscribe.git
cd OpenTranscribe
# Make utility script executable
chmod +x opentr.sh
Environment Configuration
# Copy environment template
cp .env.example .env
# Edit .env file with your settings (optional for development)
# Key variables:
# - HUGGINGFACE_TOKEN (required for speaker diarization)
# - GPU settings for optimal performance
Start OpenTranscribe
# Start in development mode (with hot reload)
./opentr.sh start dev
# Or start in production mode
./opentr.sh start prod
Access the Application
--with-monitoring)--with-monitoring)The opentr.sh script provides comprehensive management for all application operations:
# Start the application
./opentr.sh start [dev|prod] # Start in development or production mode
./opentr.sh start dev --gpu-scale # Start with multi-GPU scaling (optional)
./opentr.sh stop # Stop all services
./opentr.sh status # Show container status
./opentr.sh logs [service] # View logs (all or specific service)
Two independent scaling modes are available — choose based on your hardware and workload:
Option A — GPU Scale (multiple parallel pipelines on one GPU):
# The --gpu-scale flag is what enables scaling — GPU_SCALE_ENABLED in .env does
# NOT turn it on (it only affects which GPU the system-stats display queries).
GPU_SCALE_DEVICE_ID=2 # Which GPU to use (default: 2)
GPU_SCALE_WORKERS=4 # Number of parallel workers (default: 4)
# Start with GPU scaling
./opentr.sh start dev --gpu-scale
./opentr.sh reset dev --gpu-scale
# Example: GPU 2 (A6000) runs 4 parallel workers; GPU 0/1 handle other tasks
Best for: High file throughput — processes 4 videos simultaneously on one GPU.
Option B — GPU Split (transcription and diarization on separate GPUs):
# Configure in .env
GPU_TRANSCRIBE_DEVICE_ID=0 # GPU for WhisperX (transcription)
GPU_DIARIZE_DEVICE_ID=1 # GPU for PyAnnote (diarization)
ENGINE_SHARED_VOLUME_PATH=/scratch/opentranscribe/engine # per-task handoff dir on the pipeline_scratch volume
# Start with GPU split
./opentr.sh start dev --with-gpu-split
./opentr.sh reset dev --with-gpu-split
Best for: Two-GPU setups where you want dedicated VRAM per model — one GPU purely for Whisper, one purely for PyAnnote.
📖 Deployment reference: For a full table of every deployment type and its exact
./opentr.shcommand — plus the first-init healthcheck model, the cross-worker scratch-volume contract, all three GPU modes, the security posture (loopback infra ports,no-new-privileges, secret generation), and the NAS/NVMe storage overlay — see the Deployment Configuration operations guide.
# Mount a host folder to watch for new media (the only watch env var),
# then start with the watch overlay:
WATCH_HOST_PATH=/path/to/your/media ./opentr.sh start dev --with-watch
# Optional: a local Samba share to test an SMB watch source
./opentr.sh start dev --with-watch --with-smb-test
# Seed sample media (multi-part group, duplicate, old file, mixed types)
bash scripts/setup-watch-source-test-data.sh ./watch
Then configure sources in Settings → Watch Sources (local folder, S3, or SMB). Without --with-watch, the local-folder type is hidden and only S3/SMB are available. All connection, schedule, and credential settings are managed in the UI — no restart required.
# Brand-new isolated stack: own compose project + named volumes, NAS overlay
# NEVER loaded, real data untouched. Runs on the standard dev ports by default
# (refuses to start if the main stack already holds them).
./opentr.sh start dev --fresh test1
# Run side-by-side with the main stack by offsetting every published port:
./opentr.sh start dev --fresh test1 --port-offset 100 # backend :5274, frontend :5273, ...
# Upload a couple of small sample files once the stack is healthy:
./opentr.sh start dev --fresh test1 --seed-benchmark
# Manage fresh deployments:
./opentr.sh stop --fresh test1 # stop (keep volumes)
./opentr.sh status --fresh test1 # status
./opentr.sh fresh-list # list all fresh deployments + volumes
./opentr.sh fresh-destroy test1 # remove containers + volumes (confirmed)
# See exactly where your live data lives before deleting anything:
./opentr.sh data-paths
Fresh deployments are the safe way to spin up throwaway stacks. They use an
isolated otfresh-<name> compose project (separate containers and named
volumes), and the NAS/bind-mount overlay is never attached — so the production
dataset can never be touched. The non-fresh start auto-loads the NAS overlay
when storage paths are set in .env (with a prominent banner); pass --no-nas
to suppress it. Add --dry-run to any start to print the exact compose files
and command without launching anything.
# Start the optional observability stack alongside the app
./opentr.sh start dev --with-monitoring
Prometheus scrapes the backend's /metrics endpoint; Grafana (:5185, default login admin / $GRAFANA_PASSWORD) ships with pre-provisioned ops and product dashboards. The overlay is fully optional — omit the flag and the stack runs unchanged. See Monitoring & Logging for the dashboard tour, JSON access-log analysis, and AWS notes.
# Mount a backup destination, then configure schedule/destination in the admin UI
./opentr.sh start dev --with-backup
Built-in scheduled database backups run on the existing celery-beat service — no host cron. Configure everything in Settings → System Management → Backups: cron schedule, GFS retention, optional gpg encryption, and a destination that is either a mounted folder or an S3-compatible bucket (AWS S3 / MinIO / Backblaze — keeps backups off the host machine). See Backup & Restore.
If the database is ever lost but the MinIO media survives, Storage Recovery rebuilds the catalog in place (python -m app.scripts.reingest_minio) — no re-download, no duplication.
# Service management
./opentr.sh restart-backend # Restart API and workers without database reset
./opentr.sh restart-frontend # Restart frontend only
./opentr.sh restart-all # Restart all services without data loss
# Container rebuilding (after code changes)
./opentr.sh rebuild-backend # Rebuild backend with new code
./opentr.sh rebuild-frontend # Rebuild frontend with new code
./opentr.sh build # Rebuild all containers
# Data operations (⚠️ DESTRUCTIVE)
./opentr.sh reset [dev|prod] # Complete reset - deletes ALL data!
# Alembic migrations run automatically on dev backend startup — no separate init command needed.
# Backup and restore
./opentr.sh backup # Create timestamped database backup
./opentr.sh backup --encrypt # GPG-encrypted backup (AES-256, no plaintext on disk)
./opentr.sh restore [--yes] [--no-safety-dump] [--from-s3] <file> # REPLACE the database from a backup
# (.sql, .dump, .sql.gpg, .dump.gpg; --from-s3 fetches by name first) — destructive
# Production installs (no repo clone, no opentr.sh) use the identical commands via the
# shipped management script instead: ./opentranscribe.sh backup / restore — same flags,
# same behavior. See docs-site/docs/operations/backup-restore.md.
# Maintenance
./opentr.sh health # Check service health status
./opentr.sh shell [service] # Open shell in container
# Available services: backend, frontend, postgres, redis, minio, opensearch, celery-worker
# View specific service logs
./opentr.sh logs backend # API server logs
./opentr.sh logs celery-worker # AI processing logs
./opentr.sh logs frontend # Frontend development logs
./opentr.sh logs postgres # Database logs
# Follow logs in real-time
./opentr.sh logs backend -f
User Registration
Upload or Record Content
Monitor Processing
Explore Your Content
Configure AI Features (Optional)
🎙️ Device Selection → 📊 Level Monitoring → ⏸️ Session Control → ⬆️ Background Upload
🤖 LLM Configuration → 📝 Custom Prompts → 🔍 Content Analysis → 📊 BLUF Summaries
👥 Automatic Detection → 🤖 AI Recognition → 🏷️ Profile Management → 🔍 Cross-Media Tracking
⬆️ Concurrent Uploads → 📊 Progress Tracking → 🔄 Retry Logic → 📋 Queue Management
🔍 Keyword Search → 🧠 Semantic Search → 🏷️ Smart Filtering → 🎯 Waveform Navigation
📁 Create Collections → 📂 Organize Files → 🏷️ Bulk Operations → 🎯 Inline Editing
🔔 Progress Updates → 📊 Status Tracking → 🔄 WebSocket Integration → ✅ Completion Alerts
📄 Multiple Formats → 📺 Subtitle Files → 🔗 API Access → 🎬 Media Downloads
OpenTranscribe/
├── 📁 backend/ # Python FastAPI backend
│ ├── 📁 app/ # Application modules
│ │ ├── 📁 api/ # REST API endpoints
│ │ ├── 📁 models/ # Database models
│ │ ├── 📁 services/ # Business logic
│ │ ├── 📁 tasks/ # Background AI processing
│ │ ├── 📁 utils/ # Common utilities
│ │ └── 📁 db/ # Database configuration
│ ├── 📁 scripts/ # Admin and maintenance scripts
│ ├── 📁 tests/ # Comprehensive test suite
│ └── 📄 README.md # Backend documentation
├── 📁 frontend/ # Svelte frontend application
│ ├── 📁 src/ # Source code
│ │ ├── 📁 components/ # Reusable UI components
│ │ ├── 📁 routes/ # Page components
│ │ ├── 📁 stores/ # State management
│ │ └── 📁 styles/ # CSS and themes
│ └── 📄 README.md # Frontend documentation
├── 📁 database/ # Database initialization
├── 📁 models_ai/ # AI model storage (runtime)
├── 📁 scripts/ # Utility scripts
├── 📄 docker-compose.yml # Container orchestration
├── 📄 opentr.sh # Main utility script
└── 📄 README.md # This file
# Database
DATABASE_URL=postgresql://postgres:password@postgres:5432/opentranscribe
# Security
SECRET_KEY=your-super-secret-key-here
JWT_SECRET_KEY=your-jwt-secret-key
# Object Storage
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_BUCKET_NAME=transcribe-app
There is no single AUTH_TYPE switch — every method is enabled independently, and all of them
can run at once (each account records which one owns it). These .env values are only a
bootstrap seed / fallback: Settings → Authentication in the admin UI is DB-backed and takes
precedence over .env, with no restart required.
# LDAP/Active Directory
LDAP_ENABLED=false
LDAP_SERVER=ldap://your-ldap-server:389
LDAP_BASE_DN=dc=example,dc=com
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_PASSWORD=your-bind-password
# OpenID Connect (any conforming provider, including Keycloak — the surface used to
# be Keycloak-specific; the legacy KEYCLOAK_* names still work as a permanent alias
# for OIDC_*, and win if both are set)
OIDC_ENABLED=false
OIDC_SERVER_URL=https://your-idp-server
OIDC_REALM=your-realm
OIDC_CLIENT_ID=opentranscribe
OIDC_CLIENT_SECRET=your-client-secret
# SAML 2.0
SAML_ENABLED=false
# PKI/X.509
PKI_ENABLED=false
PKI_CA_CERT_PATH=/path/to/ca-cert.pem
PKI_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 # required whenever PKI is enabled
# Trusted-header (reverse proxy)
PROXY_ENABLED=false
PROXY_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 # required whenever proxy auth is enabled
# MFA (optional, works with any auth type)
MFA_ENABLED=false
MFA_ISSUER=OpenTranscribe
See detailed setup guides: LDAP | OIDC | PKI | SAML | Trusted-header proxy
# Required for speaker diarization - see setup instructions below
HUGGINGFACE_TOKEN=your_huggingface_token_here
# Model configuration
WHISPER_MODEL=large-v3-turbo # large-v3-turbo (default), large-v3, large-v2, medium, small, base
COMPUTE_TYPE=float16 # float16, int8
BATCH_SIZE=16 # Reduce if GPU memory limited
# Speaker detection
MIN_SPEAKERS=1 # Minimum speakers to detect
MAX_SPEAKERS=20 # Maximum speakers to detect (can be increased to 50+ for large conferences)
# Model caching (recommended)
MODEL_CACHE_DIR=./models # Directory to store downloaded AI models
OpenTranscribe offers flexible AI deployment options. Choose the approach that best fits your infrastructure:
🔧 Quick Setup Options:
Cloud-Only (Recommended for Most Users)
# Configure for OpenAI in .env
LLM_PROVIDER=openai
OPENAI_API_KEY=your_openai_key
OPENAI_MODEL_NAME=gpt-4o-mini
# Start without local LLM
./opentr.sh start dev
Local vLLM (Self-Hosted)
# Deploy vLLM server separately, then configure in .env
LLM_PROVIDER=vllm
VLLM_BASE_URL=http://your-vllm-server:8000/v1
VLLM_MODEL_NAME=gpt-oss-20b
# Start OpenTranscribe
./opentr.sh start dev
Local Ollama (Self-Hosted)
# Deploy Ollama server separately, then configure in .env
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://your-ollama-server:11434
OLLAMA_MODEL_NAME=llama3.2:3b-instruct-q4_K_M
# Start OpenTranscribe
./opentr.sh start dev
📋 Complete Provider Configuration:
# Cloud Providers (configure in .env)
LLM_PROVIDER=openai # openai, anthropic, custom (openrouter)
OPENAI_API_KEY=your_openai_key # OpenAI GPT models
ANTHROPIC_API_KEY=your_claude_key # Anthropic Claude models
OPENROUTER_API_KEY=your_or_key # OpenRouter (multi-provider)
# Local Providers (requires additional Docker services)
LLM_PROVIDER=vllm # Local vLLM server
LLM_PROVIDER=ollama # Local Ollama server
🎯 Deployment Scenarios:
LLM_PROVIDER empty. Transcription, diarization, redaction and full hybrid search (keyword + semantic) all still work — only summaries, topic suggestions, speaker-ID hints and AI Chat need a providerSee LLM Integration for detailed setup instructions.
OpenTranscribe automatically downloads and caches AI models for optimal performance. Models are saved locally and reused across container restarts.
Default Setup:
./models/ directory in your project folderDirectory Structure:
./models/
├── huggingface/ # PyAnnote + WhisperX models
│ ├── hub/ # WhisperX transcription models (~1.5GB)
│ └── transformers/ # PyAnnote transformer models
└── torch/ # PyTorch cache
└── pyannote/ # PyAnnote diarization models (~500MB)
Custom Cache Location:
# Set custom directory in your .env file
MODEL_CACHE_DIR=/path/to/your/models
# Examples:
MODEL_CACHE_DIR=~/ai-models # Home directory
MODEL_CACHE_DIR=/mnt/storage/models # Network storage
MODEL_CACHE_DIR=./cache # Project subdirectory
Storage Requirements:
OpenTranscribe requires a HuggingFace token for speaker diarization and voice fingerprinting features. Follow these steps:
You MUST accept the user agreement for the PyAnnote diarization model or speaker diarization will fail:
⚠️ Common Issue: If the agreement isn't accepted, downloads will fail with
'NoneType' object has no attribute 'eval'or an HTTP 403/PermissionError. Older docs mentionedpyannote/segmentation-3.0andpyannote/speaker-diarization-3.1— that pair is optional and only helps the in-process PyAnnote engine's internal last-resort fallback; it is never a substitute for acceptingcommunity-1.
Add your token to the environment configuration:
For Production Installation:
# The setup script will prompt you for your token
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
For Manual Installation:
# Add to .env file
echo "HUGGINGFACE_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" >> .env
Note: Without a valid HuggingFace token, speaker diarization will be disabled and speakers will not be automatically detected or identified across different media files.
# GPU settings
USE_GPU=true # Enable GPU acceleration
CUDA_VISIBLE_DEVICES=0 # GPU device selection
# Resource limits
MAX_UPLOAD_SIZE=4GB # Maximum file size (supports GoPro videos)
CELERY_WORKER_CONCURRENCY=2 # Concurrent tasks
For production use, ensure you:
Security Configuration
# Generate strong secrets
openssl rand -hex 32 # For SECRET_KEY
openssl rand -hex 32 # For JWT_SECRET_KEY
# Set strong database passwords
# Configure proper firewall rules
# Set up SSL/TLS certificates
Performance Optimization
# Use production environment
NODE_ENV=production
# Configure resource limits
# Set up monitoring and logging
# Configure backup strategies
HTTPS/SSL Setup (Required for microphone recording from other devices)
OpenTranscribe includes built-in NGINX reverse proxy support with SSL/TLS:
# Quick setup for homelab/local network
./scripts/generate-ssl-cert.sh opentranscribe.local --auto-ip
# Add to .env
NGINX_SERVER_NAME=opentranscribe.local
# Start with HTTPS enabled
./opentr.sh start dev
For detailed instructions including Let's Encrypt setup, see docs/NGINX_SETUP.md.
Note: Modern browsers require HTTPS for microphone access. Without NGINX/SSL setup, microphone recording will only work when accessing via
localhost.
# Start development with hot reload
./opentr.sh start dev
# Backend development
cd backend/
pip install -r requirements.txt
pytest tests/ # Run tests
ruff format app/ # Format code
ruff check app/ # Lint code
# Frontend development
cd frontend/
npm install
npm run dev # Development server
npm run test # Run tests
npm run lint # Lint code
Releases run through one script — don't hand-run git tag, docker push, or
gh release:
./scripts/release.sh status # where am I?
./scripts/release.sh reset 0.5.0 # clear rehearsal history before a real run
./scripts/release.sh preflight 0.5.0 # seconds — fails fast on the usual suspects
./scripts/release.sh run 0.5.0 # the whole sequence
./scripts/release.sh run 0.5.0 --dry-run # print every command, execute nothing
Twelve stages, each independently runnable, skippable (--skip) and resumable
(--from):
preflight → bump → verify → test → build → scan → rehearse
→ tag → publish → smoke → promote → finish
The last four are the only ones that reach Docker Hub or GitHub, and each needs
an explicit --yes. Before they run, two rehearsal scenarios prove the release
end to end on real data: a fresh install via the documented one-liner, and an
in-place upgrade from the previous published release — including a file
uploaded after the upgrade, to prove the upgraded stack still does its job.
📖 Full guide: Developer Guide → Releasing
Testing is local-first: GitHub Actions runs the unit/API suite as a safety net, but the complete suite (S3/OpenSearch integration, browser E2E) needs the live dev stack and runs locally.
# The pre-merge gate — runs EVERYTHING against the live stack
# (ungated suite, security-gated suites in both FIPS modes, integration tests)
./scripts/run-integration-tests.sh # add --coverage / --e2e-smoke
# Backend tests (host venv; MinIO/OpenSearch tests auto-enable when the stack is up)
source backend/venv/bin/activate
cd backend/
pytest tests/ # All tests
pytest tests/api/ # API tests only
pytest --cov=app tests/ # With coverage (report-only, no threshold yet)
# Frontend tests
cd frontend/
npm run test # Vitest unit + component tests (jsdom)
npm run test:coverage # …with coverage
npm run check # svelte-check (types + a11y)
npm run lint # ESLint (flat config)
npm run check:i18n # locale key-parity across all 8 languages
# Browser end-to-end (Playwright via pytest, against the live stack)
./scripts/e2e/run-e2e.sh # full e2e suite, headless
./scripts/e2e/run-e2e-smoke.sh # quick read-mostly subset
./scripts/e2e/run-e2e.sh -m upload # one marker: upload/search/settings/
# transcription/gallery/auth/visual
pytest backend/tests/e2e/test_a11y.py -v # axe-core accessibility
pytest backend/tests/e2e/test_visual_regression.py -v # screenshot baselines
Tools that keep the suite honest. A test that cannot fail is worse than no test — it buys false confidence and hides the defect it was written to catch. These four exist because this repo had shipped every one of those failure modes: an assertion that passed against an empty index, a marker that selected no tests, 240 security tests gated off behind stale environment variables, and an endpoint returning a hardcoded value that no test referenced.
python3 scripts/audit-tests.py backend/tests # 16 AST detectors, exits 1 on new offenders
cd frontend && npm run test:audit # the vitest sibling, 10 detectors
npm run test:audit:selftest # ...and ITS self-test — not optional
python3 scripts/analyze-test-timing.py <junit.xml> [--baseline baseline.xml]
./scripts/run-mutation-tests.sh --module spans # opt-in; never in the gate or CI
file::test::category —
keyed by test alone, one entry once exempted a test from every detector at once.analyze-test-timing.py finds barriers, not just slow tests. Unrelated tests from
many files sharing a sub-second duration band is a released lock queue, not a
coincidence — that is how one worker was found owning 81% of the wall clock.python -m cProfile -o out.prof -m pytest <test> settled in one pass what two plausible hypotheses had cost two full measurement
cycles.Current (measured 2026-08-13): backend 6,623 passed / 62 real skips / 104 s (from
4,752 / 458 / 511 s); frontend 669 passed / 76 files / 21.6 s; e2e 341 collected.
A residual ~9 s DDL cluster remains (the ddl_exclusive advisory-lock queue); the
sub-second barriers are gone. Re-derive rather than trust these — the values printed
here previously were wrong by 1,294 backend and 188 frontend tests;
./scripts/run-backend-tests.sh --summary answers in seconds.
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
# Check GPU availability
nvidia-smi
# Verify Docker GPU support
docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
# Set CPU-only mode if needed
echo "USE_GPU=false" >> .env
Symptoms:
Permission denied: '/home/appuser/.cache/huggingface/hub'Permission denied: '/home/appuser/.cache/yt-dlp'Cause: Docker creates model cache directories with root ownership, but containers run as non-root user (UID 1000) for security.
Solution:
# Option 1: Run the automated permission fix script (recommended)
cd opentranscribe # Or your installation directory
./scripts/fix-model-permissions.sh
# Option 2: Manual fix using Docker
docker run --rm -v ./models:/models busybox chown -R 1000:1000 /models
# Option 3: Manual fix using sudo (if available)
sudo chown -R 1000:1000 ./models
sudo chmod -R 755 ./models
Prevention for New Installations:
curl -fsSL https://raw.githubusercontent.com/attevon-llc/OpenTranscribe/master/setup-opentranscribe.sh | bash
Why This Happens:
Verification:
# Check directory ownership (should show UID 1000 or your user)
ls -la models/
# Test write permissions
touch models/huggingface/test.txt && rm models/huggingface/test.txt
# Reduce model size
echo "WHISPER_MODEL=medium" >> .env
echo "BATCH_SIZE=8" >> .env
echo "COMPUTE_TYPE=int8" >> .env
# Monitor memory usage
docker stats
USE_GPU=true)WHISPER_MODEL=medium)# Reset database
./opentr.sh reset dev
# Check database logs
./opentr.sh logs postgres
# Verify database is running
./opentr.sh shell postgres psql -U postgres -l
# Check service status
./opentr.sh status
# Full reset (⚠️ deletes all data)
./opentr.sh reset dev
For systems where the GPU cannot fit the full transcription model, OpenTranscribe automatically activates hybrid mode: transcription runs on CPU while diarization stays on GPU. This requires only ~1.3 GB VRAM for PyAnnote and delivers speaker-diarized transcripts without a dedicated GPU. This is a Linux/WSL2-with-NVIDIA-GPU feature — there is no GPU/MPS path available on macOS (Docker Desktop has no Metal passthrough), so macOS deployments use the --lite (CPU-only) image instead and run both stages on CPU.
| Scenario | Transcription | Diarization | Trigger |
|---|---|---|---|
| GPU ≥ 8 GB + large-v3-turbo | GPU | GPU | Normal mode |
| GPU 4–6 GB + large-v3-turbo | CPU (small model) | GPU | Auto hybrid |
| macOS (any Apple Silicon) | CPU (small model) | CPU (--lite image) | Always CPU-only — no MPS in Docker |
WHISPER_HYBRID_MODE=true | CPU (small model) | GPU | Manual override (Linux/WSL2 + NVIDIA GPU only) |
The CPU model defaults to small (int8, ~15–30× real-time on modern hardware). Override with WHISPER_HYBRID_CPU_MODEL=medium for better accuracy at the cost of speed.
# Force hybrid mode on (useful for testing or shared-GPU deployments)
WHISPER_HYBRID_MODE=true
WHISPER_HYBRID_CPU_MODEL=small # small | medium | base
# Force hybrid mode off (never auto-activate)
WHISPER_HYBRID_MODE=false
# Auto-detect (default — recommended)
WHISPER_HYBRID_MODE=auto
# GPU optimization (≥ 8 GB VRAM)
COMPUTE_TYPE=float16 # Use half precision
BATCH_SIZE=auto # Auto-tuned per model (turbo→16, medium→24, small→24)
WHISPER_MODEL=large-v3-turbo # Default: fast and accurate; use large-v3 for translation or max accuracy
# Hybrid mode (low-VRAM GPU or macOS — CPU transcription + GPU diarization)
WHISPER_HYBRID_MODE=auto # Auto-activates when GPU VRAM is insufficient; always on for macOS
WHISPER_HYBRID_CPU_MODEL=small # Transcription model used in hybrid mode (small | medium | base)
# CPU-only (no GPU)
WHISPER_HYBRID_MODE=true # Force CPU transcription
WHISPER_HYBRID_CPU_MODEL=small # small (good accuracy) or base (faster, lower accuracy)
OpenTranscribe supports multiple authentication methods for enterprise and government deployments:
/scim/v2 (RFC 7643/7644)For government deployments, OpenTranscribe includes features aligned with FedRAMP controls:
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) - see the LICENSE file for details.
The AGPL-3.0 license ensures that:
Built with ❤️ using AI assistance and modern open-source technologies.
OpenTranscribe demonstrates the power of AI-assisted development while maintaining full local control over your data and processing.
2,322 commits
123 commits
16 commits
12 commits
Python
58.7%
HTML
18.9%
Svelte
9.0%
Shell
5.4%
TypeScript
5.0%
C
2.2%