HasanAra is a searchable, exportable archive for HasanAbi Twitch broadcast VOD transcripts pulled from YouTube. The stack includes a FastAPI backend, PostgreSQL queue/store, a GPU-accelerated Whisper worker (ROCm or CUDA), optional pyannote diarization, and a Vite React frontend with search, deep links, and export tools.
HasanAra is credited to Subcult. You can support Subcult on Patreon.
The archive is focused on VODs from these YouTube channels:
- https://www.youtube.com/@HasanAbiVODs3
- https://www.youtube.com/@HasanAbiVODsBackup
- https://www.youtube.com/@HasanAbiVODs
Integrate HasanAra/transcript-create into your applications with the existing client libraries:
-
Python SDK - Async client with full type hints
pip install transcript-create-client
-
JavaScript/TypeScript SDK - Universal client for Node.js and browsers
npm install @transcript-create/sdk
See the SDK documentation for detailed usage examples and API reference.
- Getting Started Guide - Set up and run your first transcription
- YouTube Ingestion Setup - JS runtime, cookies, and troubleshooting
- YouTube Quick Reference - Fast troubleshooting for common issues
- PO Token Manager - Configure YouTube PO tokens for reliable downloads
- API Documentation - Complete API reference with examples
- Contributing Guide - How to contribute to the project
- Code of Conduct - Community guidelines
- Changelog - Track project changes and releases
- Security Policy - Report vulnerabilities responsibly
- First-Time Contributors - Step-by-step guide for newcomers
- Development Setup - Set up your local environment
- Architecture Overview - System design and components
- Code Guidelines - Coding standards and best practices
- Testing Guide - How to write and run tests
- Release Process - Versioning and releases
- 🐛 Report a Bug
- ✨ Request a Feature
- ❓ Ask a Question
- 🙌 View All Contributors
- 🎯 Find Good First Issues
This project has comprehensive CI/CD automation:
- Backend CI: Linting (ruff, black, isort), type checking (mypy), security scanning, tests with PostgreSQL, Docker build validation
- Frontend CI: ESLint, Prettier formatting, TypeScript checking, Vite build with bundle size monitoring
- Database Migrations: Alembic migration validation on fresh and existing databases, up/down testing
- E2E Tests: Playwright tests across Chromium, Firefox, WebKit, and mobile viewports (255 tests)
- Docker Build: Automated builds on push to main and tags, published to GHCR with ROCm support
- Security: Weekly dependency audits, secret scanning, vulnerability detection
All checks must pass before merging to main. Typical PR checks complete in < 3 minutes. See CONTRIBUTING.md for details.
- Ingest a single VOD or entire channel with yt-dlp
- Transcribe with Whisper large-v3 (GPU preferred) and optional speaker diarization
- Long-audio chunking with automatic time-offset merging
- YouTube auto-captions ingestion to compare vs native transcripts
- Full-text search across native or YouTube captions (Postgres FTS or OpenSearch)
- Exports: SRT, VTT, JSON (native and YouTube), plus pretty PDF with headers/footers
- OAuth login (Google, Twitch), favorites, admin analytics
- Monetization-ready: free vs pro plans, daily limits, Stripe checkout/portal/webhook
- Horizontal-safe worker queue using Postgres SKIP LOCKED
- API (FastAPI): modular routers for auth, billing, jobs, videos, favorites, events, admin, search, exports
- Database (PostgreSQL):
jobs,videos,transcripts,segments, plus YouTube captions tables - Worker: selects one pending video with
FOR UPDATE SKIP LOCKED, runs the pipeline, updates status - Optional: OpenSearch for richer search and highlighting
- Frontend (Vite + React + Tailwind): search/group-by-video, timestamp deep links, export menu, pricing/upgrade flow
Why this design: a DB-backed queue keeps infra light while enabling scale-out workers. Chunking manages memory/runtime; diarization runs post-process for coherent speakers across the whole audio.
JavaScript Runtime Required: yt-dlp now requires a JavaScript runtime (Deno/Node/Bun/QuickJS) to solve YouTube challenges. Install one before starting:
Deno (Recommended):
# Linux/macOS
curl -fsSL https://deno.land/install.sh | sh
# Windows
irm https://deno.land/install.ps1 | iex
# Homebrew
brew install denoAlternatives:
- Node.js: https://nodejs.org/ or
brew install node - Bun: https://bun.sh/ or
curl -fsSL https://bun.sh/install | bash - QuickJS: https://bellard.org/quickjs/ or
brew install quickjs
After installation, ensure the runtime is in your PATH and configure it in .env if using a non-default runtime.
- Copy env and fill basics
cp .env.example .env
# SECURITY: Generate a secure SESSION_SECRET
openssl rand -hex 32 # Copy this value to SESSION_SECRET in .env
# Required for local dev: DATABASE_URL (set automatically by docker-compose)
# Optional: FRONTEND_ORIGIN (defaults to http://localhost:5173), HF_TOKEN for diarization
# Billing/auth can be added later; see sections belowSecurity Note: See SECURITY.md for detailed security practices and secrets management.
- Start services with Docker Compose (Postgres + API + Worker + Backup; OpenSearch optional)
docker compose build
docker compose up -d- API available at http://localhost:8000
- Postgres exposed on host port 5434 (inside network: db:5432)
- Prometheus metrics at http://localhost:9090
- Grafana dashboards at http://localhost:3000 (admin/admin)
- Automated backups: Daily at 2 AM UTC (database) and 3 AM UTC (media)
- Backup verification: Weekly on Sundays at 4 AM UTC
- If your host ROCm version ≠ 6.0, use a different build arg, e.g.:
docker compose build --build-arg ROCM_WHEEL_INDEX=https://download.pytorch.org/whl/rocm6.1
docker compose up -dIf ports are already in use on your machine, you can override the host ports without editing the compose file by setting environment variables:
DB_HOST_PORT(default 5434)API_HOST_PORT(default 8000)OPENSEARCH_HOST_PORT(default 9200)DASHBOARDS_HOST_PORT(default 5601)PROMETHEUS_HOST_PORT(default 9090)GRAFANA_HOST_PORT(default 3000)
Example (run Grafana on 3300 instead of 3000):
GRAFANA_HOST_PORT=3300 docker compose up -d- Start the frontend (separate terminal)
cd frontend
npm install
npm run devOpen http://localhost:5173.
- Ingest a video
curl -s -X POST http://localhost:8000/jobs \
-H 'Content-Type: application/json' \
-d '{"url":"https://www.youtube.com/watch?v=VIDEOID","kind":"single"}'Check status and fetch transcript when complete (see API section below).
Pre-built Docker images are automatically published to GitHub Container Registry (GHCR) on every release and push to main.
See CHANGELOG.md for release notes and version history.
# Pull the latest stable image
docker pull ghcr.io/onnwee/transcript-create:latest
# Pull a specific version
docker pull ghcr.io/onnwee/transcript-create:1.0.0
# Pull a specific ROCm variant
docker pull ghcr.io/onnwee/transcript-create:rocm6.0
docker pull ghcr.io/onnwee/transcript-create:rocm6.1latest- Latest stable build from main branch (ROCm 6.0 by default)v1.2.3,1.2.3,1.2,1- Semantic version tags from releasessha-abc123- Specific commit buildsrocm6.0,rocm6.1,rocm6.2- ROCm version variantsbuildcache- Build cache (internal use)
Update your docker-compose.yml to use the pre-built image:
services:
api:
image: ghcr.io/onnwee/transcript-create:latest
# Remove the 'build' section
env_file: .env
# ... rest of configuration- Base: ROCm dev-ubuntu-22.04:6.0.2 (AMD GPU support)
- Size: ~2.5-3GB (optimized with layer caching)
- Platforms: linux/amd64
- Includes: PyTorch ROCm, Whisper, ffmpeg, all dependencies
Images are built with:
- Planned: Multi-stage optimization for smaller size
- Layer caching for faster rebuilds (< 5 min with cache)
- SBOM (Software Bill of Materials) for security
- Provenance attestations for supply chain security
- Configurable ROCm version via
ROCM_WHEEL_INDEXbuild arg
- Backend:
app/(routers inapp/routes/, settings inapp/settings.py) - Worker:
worker/(pipeline + whisper runner + diarization) - Frontend:
frontend/(Vite + React + Tailwind) - Database migrations:
alembic/(Alembic migrations for schema versioning) - SQL schema:
sql/schema.sql(reference schema; migrations are applied via Alembic) - Data storage:
data/VIDEO_UUID/mounted as/datain containers - OpenSearch analysis:
config/opensearch/analysis/ - Documentation:
docs/(guides for migrations, logging, monitoring, disaster recovery, etc.)- Database Migrations - Schema versioning with Alembic
- Backup & Disaster Recovery - Automated backups, PITR, recovery procedures
- Backup Operations - Daily operations, troubleshooting, maintenance
See docs/MIGRATIONS.md for database migration details. See docs/operations/disaster-recovery.md for backup and recovery procedures.
- Fetch metadata and download audio (yt-dlp)
- Ensure 16 kHz mono WAV via ffmpeg
- Chunk long audio (
CHUNK_SECONDS, default 900s) - Transcribe each chunk (Whisper CT2 or PyTorch)
- Optional diarization and alignment (pyannote) if
HF_TOKENis set - Persist one
transcriptsrow and manysegmentsrows; mark videocompleted
States progress from downloading → transcoding → transcribing → completed or failed. Worker requeues stalled videos based on RESCUE_STUCK_AFTER_SECONDS.
Single endpoint for both native or YouTube captions:
GET /search?q=hello&source=native|youtube
- Postgres FTS:
SEARCH_BACKEND=postgres(GIN indices and triggers included insql/schema.sql) - OpenSearch:
SEARCH_BACKEND=opensearch(indices with synonyms/n-grams set up by the indexer script)
Indexer examples:
python scripts/backfill_fts.py --batch 500 --until-empty
python scripts/opensearch_indexer.py --recreate --batch 5000 --bulk-docs 1000 --refresh-off- Native:
/videos/{id}/transcript.(json|srt|vtt|pdf) - YouTube captions:
/videos/{id}/youtube-transcript.(json|srt|vtt)
PDF export uses ReportLab with a serif font (set via PDF_FONT_PATH) and includes headers/footers/metadata. Export requests are logged for analytics. Free plans may be gated by daily limits; HTML requests to gated endpoints redirect to an upgrade route.
- OAuth providers: Google and Twitch
- Session cookie with server-side lookup;
/auth/mereturns plan/quota - Admins (by email) can view analytics and set user plans
- Free vs Pro: configurable daily limits for search and exports
Frontend includes a pricing/upgrade flow and an interstitial upgrade page that returns users to their original destination after upgrading.
Endpoints
POST /billing/checkout-session→ returns a Checkout URLGET /billing/portal→ returns a Customer Portal URLPOST /stripe/webhook→ subscription lifecycle (set plan to Pro on active/trialing; revert to Free on cancellation)
Environment
STRIPE_API_KEY=sk_...STRIPE_PRICE_PRO_MONTHLY/STRIPE_PRICE_PRO_YEARLY=price_...STRIPE_WEBHOOK_SECRET=whsec_...STRIPE_SUCCESS_URL/STRIPE_CANCEL_URLuse{origin}which resolves toFRONTEND_ORIGIN
Tip: For local testing, use Stripe CLI to forward events to http://localhost:8000/stripe/webhook and create a Checkout session from the Pricing page or via the API.
Jobs
POST /jobs→{ url: string, kind: "single"|"channel" }→ enqueuesGET /jobs/{id}→ status
Videos
GET /videos/{id}→ detailsGET /videos/{id}/transcript→ merged segments (JSON)?mode=raw(default) → raw Whisper segments?mode=cleaned→ segments with cleanup (filler removal, punctuation, normalization)?mode=formatted→ fully formatted text with speaker labels and paragraphs
GET /videos/{id}/transcript.(srt|vtt|pdf)GET /videos/{id}/youtube-transcript.(json|srt|vtt)
Transcript Modes:
The /videos/{id}/transcript endpoint supports three modes via the mode query parameter:
-
raw (default): Returns raw Whisper-generated segments without any processing
curl http://localhost:8000/videos/{id}/transcript # or explicitly curl http://localhost:8000/videos/{id}/transcript?mode=raw -
cleaned: Returns segments with cleanup applied (filler removal, normalization, punctuation)
curl http://localhost:8000/videos/{id}/transcript?mode=cleanedResponse includes:
text_raw: Original texttext_cleaned: Text after cleanupcleanup_config: Configuration usedstats: Cleanup statistics (fillers removed, punctuation added, etc.)
-
formatted: Returns fully formatted text with speaker labels and paragraph structure
curl http://localhost:8000/videos/{id}/transcript?mode=formattedResponse includes:
text: Formatted transcript textformat: Formatting style (inline/dialogue/structured)cleanup_config: Configuration used
All modes return responses with ETags for efficient caching. ETags are unique per mode to avoid stale caches.
Search
GET /search?q=...&source=native|youtube→ grouped hits with timestamps and highlights
Admin
POST /admin/users/{user_id}/planbody{ "plan": "free"|"pro" }GET /admin/events//admin/events.csv//admin/events/summary
Billing
POST /billing/checkout-session(body can includeperiod: "monthly"|"yearly")GET /billing/portal
Auth
GET /auth/me,GET /auth/login/google,GET /auth/login/twitch, callbacks,POST /auth/logout
Health
GET /health→ basic health check for load balancersGET /live→ Kubernetes liveness probeGET /ready→ Kubernetes readiness probe (checks critical dependencies)GET /health/detailed→ comprehensive component status (database, OpenSearch, storage, worker)
See docs/health-checks.md for detailed health check documentation.
All endpoints return structured JSON or a redirect/byte stream where applicable. See app/routes/ for full definitions.
The backend reads .env via pydantic-settings in app/settings.py. Highlights:
- Core:
DATABASE_URL,SESSION_SECRET,FRONTEND_ORIGIN,ADMIN_EMAILS - Whisper/GPU:
WHISPER_BACKEND,WHISPER_MODEL,FORCE_GPU,GPU_DEVICE_PREFERENCE(e.g.,hip,cuda),GPU_COMPUTE_TYPES,GPU_MODEL_FALLBACKS - Chunking/cleanup:
CHUNK_SECONDS,CLEANUP_*,RESCUE_STUCK_AFTER_SECONDS - Diarization:
HF_TOKEN(optional) - Search:
SEARCH_BACKEND,OPENSEARCH_* - Quotas/plans:
FREE_DAILY_SEARCH_LIMIT,FREE_DAILY_EXPORT_LIMIT,PRO_PLAN_NAME - PDF:
PDF_FONT_PATH - Stripe: as listed in the Billing section above
Frontend env: frontend/.env supports VITE_API_BASE to point the web app at a non-default API origin.
Consult .env.example for a complete list and defaults. Compose sets DATABASE_URL to the internal db service automatically.
Generate bounded period-summary proposals with a local Ollama model. The command is a dry run unless --apply is passed, and it rejects claims without known transcript evidence IDs.
python3 scripts/generate_archive_summaries.py --granularity month --limit 3
python3 scripts/generate_archive_summaries.py --granularity month --period 2026-04 --applyConfigure OLLAMA_URL and ARCHIVE_SUMMARY_MODEL as needed. On Almaz, the archive intelligence refresher joins the external dev network so it can reach the existing ollama service. Summary generation expands each topic moment with nearby Whisper or YouTube transcript context before prompting the model.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Terminal 1 – API
uvicorn app.main:app --reload --port 8000
# Terminal 2 – Worker
python -m worker.loop
# Terminal 3 – Frontend
cd frontend && npm install && npm run dev- Compose passes
/dev/kfdand/dev/driand adds thevideogroup for ROCm - Set
GPU_DEVICE_PREFERENCE=hip,cudato try ROCm first, then CUDA, or vice versa - To force GPU and fail fast when not available:
FORCE_GPU=true - If VRAM is tight, reduce
WHISPER_MODELor setGPU_MODEL_FALLBACKS
- Compose includes OpenSearch and Dashboards on ports 9200/5601 with security disabled for local dev
- Configure
SEARCH_BACKEND=opensearchand runscripts/opensearch_indexer.pyto create/populate indices - Synonyms live in
config/opensearch/analysis/synonyms.txt
The application includes comprehensive monitoring with Prometheus metrics and Grafana dashboards.
Quick Access:
- Grafana dashboards: http://localhost:3000 (admin/admin)
- Prometheus: http://localhost:9090
- API metrics: http://localhost:8000/metrics
- Worker metrics: http://localhost:8001/metrics
Pre-configured Dashboards:
- Overview: Service health, request rates, job statistics, queue depth
- API Performance: Request rates, latency percentiles, error rates, concurrent requests
- Transcription Pipeline: Processing durations, queue status, model performance
Key Metrics:
- HTTP request rates and latency
- Job creation and completion rates
- Video processing pipeline stages
- Whisper model load and transcription times
- Database query performance
- GPU memory usage (when available)
For detailed documentation, see docs/MONITORING.md including:
- Adding custom metrics
- Alert configuration
- Troubleshooting
- Performance tuning
For comprehensive troubleshooting of YouTube downloads, see YouTube Ingestion Setup Guide.
Quick fixes:
-
JavaScript runtime not found: If API or worker fails to start with "JavaScript runtime not available":
- Install a JS runtime (Deno recommended): See Prerequisites section above
- Verify runtime is in PATH:
which denoorwhich node - Configure in
.env:JS_RUNTIME_CMD=denoorJS_RUNTIME_CMD=node - To bypass validation (not recommended): Set
YTDLP_REQUIRE_JS_RUNTIME=falsein.env - Full JS runtime guide →
-
403 Forbidden / Video unavailable: YouTube requires cookies and PO tokens for many videos:
- Export cookies from your browser (guide)
- Configure PO tokens (guide)
- Enable client fallback (default:
YTDLP_CLIENT_ORDER=web_safari,ios,android,tv) - Full troubleshooting guide →
-
Circuit breaker open: Too many consecutive failures triggered the breaker:
- Check logs:
docker compose logs worker | grep "circuit breaker" - Wait for cooldown (default 60s) or fix underlying issue
- Circuit breaker guide →
- Check logs:
-
PO token issues: Tokens expired, in cooldown, or provider unreachable:
- Check token status:
docker compose logs worker | grep "token" - Regenerate tokens or configure provider
- PO token troubleshooting →
- Check token status:
- Worker fails to start on GPU: verify ROCm drivers; try a different
ROCM_WHEEL_INDEXbuild arg; setFORCE_GPU=falseto allow CPU fallback when usingfaster-whisper - 402 responses on export/search: expected for Free plan beyond quotas; upgrade or adjust limits in
.env - Webhook not firing: ensure
STRIPE_WEBHOOK_SECRETmatches and that Stripe CLI or a public URL forwards to/stripe/webhook - CORS: set
FRONTEND_ORIGINto your web app origin - Schema: Compose auto-applies
sql/schema.sql; to re-apply manually:psql $DATABASE_URL -f sql/schema.sql
- YouTube Ingestion Setup - Comprehensive setup and troubleshooting
- PO Token Manager - Token configuration and troubleshooting
- Health Checks - System health monitoring
- Error Handling - Error recovery patterns
Backend (Python + pytest):
# Run all backend tests
pytest tests/
# Run specific test file
pytest tests/test_routes_auth.py
# Run with coverage
pytest --cov=app --cov=worker tests/Frontend (Vitest):
cd frontend
# Run all unit tests
npm test
# Run with UI
npm run test:ui
# Run with coverage
npm run test:coverage# Start PostgreSQL service
docker compose up -d db
# Run integration tests
pytest tests/integration/ -vFull end-to-end tests covering user workflows across browsers and mobile devices.
cd e2e
# Install dependencies (first time)
npm install
npx playwright install --with-deps
# Start services (in separate terminals)
cd .. && uvicorn app.main:app --reload --port 8000 # Backend
cd frontend && npm run dev # Frontend
# Seed test database
npm run seed-db
# Run E2E tests
npm test # All tests
npm run test:headed # With visible browser
npm run test:ui # Interactive UI mode
npm run test:critical # Fast critical tests onlyCoverage: 255 E2E tests across:
- Authentication & sessions
- Job creation & processing
- Search with filters & deeplinks
- Export features (SRT, VTT, PDF, JSON)
- Billing & quotas
- Error handling & 404 pages
- Mobile responsiveness
- Cross-browser (Chromium, Firefox, WebKit)
See docs/E2E-TESTING.md for comprehensive guide and e2e/README.md for detailed documentation.
We welcome contributions! Please read CONTRIBUTING.md for:
- Development setup instructions
- Code quality guidelines and linting
- CI/CD pipeline documentation
- Pull request process
- Branch protection requirements
# Install pre-commit hooks
./scripts/setup_precommit.sh
# Backend linting
ruff check app/ worker/ scripts/
black --check app/ worker/ scripts/
isort --check-only app/ worker/
# Frontend linting
cd frontend
npm run lint
npm run format:checkFor detailed guidelines, see CONTRIBUTING.md.
-
Install pre-commit hooks for security and code quality:
# Quick setup (recommended) ./scripts/setup_precommit.sh # Or manual setup pip install pre-commit pre-commit install
The pre-commit hooks automatically run:
- ruff: Fast Python linter for code quality and style
- black: Code formatter for consistent Python style
- isort: Import sorting for organized imports
- gitleaks: Secret detection to prevent credential leaks
- Additional checks for trailing whitespace, YAML/JSON/TOML syntax, etc.
-
Manual linting and formatting:
# Check all Python files ruff check app/ worker/ scripts/ black --check app/ worker/ scripts/ isort --check-only app/ worker/ scripts/ # Auto-fix issues ruff check --fix app/ worker/ scripts/ black app/ worker/ scripts/ isort app/ worker/ scripts/ # Type checking (optional) mypy app/ worker/
All linting tools are configured in
pyproject.tomlwith line-length=120 and consistent rules. -
Before committing, ensure security checks pass:
pre-commit run --all-files pip-audit -r requirements.txt
-
Follow security guidelines in SECURITY.md
This project includes comprehensive test coverage:
# Run all unit tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=app --cov=worker --cov-report=html
# Run specific test file
pytest tests/test_routes_jobs.py -vEnd-to-end integration tests validate complete workflows. See tests/integration/README.md for details.
# Run all integration tests
pytest tests/integration/ -v
# Run specific integration test suite
pytest tests/integration/test_job_flow.py -v
# Run with timeouts
pytest tests/integration/ --timeout=120 -vIntegration tests cover:
- Job creation and processing workflows
- Video transcription and export (SRT, PDF)
- Search functionality (native and YouTube captions)
- Worker processing and state transitions
- Authentication and authorization flows
- Billing and payment processing (mocked)
- Database integrity and concurrency
All tests run automatically in CI on every pull request:
- Unit tests with PostgreSQL
- Integration tests (subset on PR, full suite nightly)
- Security scans and linting
This project follows security best practices:
- All dependencies are pinned with specific versions
- Automated vulnerability scanning via GitHub Actions
- Pre-commit hooks prevent accidental secret commits
- Secrets managed via environment variables only
See SECURITY.md for:
- Reporting security vulnerabilities
- Secrets management guidelines
- Production security checklist
- Dependency update procedures
We follow Semantic Versioning (SemVer) for all releases.
- Latest major version: Full support (new features, bug fixes, security updates)
- Previous major version: Security updates only (for 6 months after new major release)
- Older versions: No support
- Current version: See releases
- Changelog: See CHANGELOG.md for all changes
- Release process: See docs/development/release-process.md
# Specific version (recommended for production)
ghcr.io/subculture-collective/transcript-create:v0.1.0
# Latest stable release (auto-updated)
ghcr.io/subculture-collective/transcript-create:latest
# Major version (gets minor and patch updates)
ghcr.io/subculture-collective/transcript-create:0This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
For information about third-party dependencies and their licenses, see THIRD_PARTY_NOTICES.md.
Thank you to all the amazing people who have contributed to Transcript Create! 🙏
See CONTRIBUTORS.md for the full list of contributors.
Want to contribute? Check out our Contributing Guide and First-Time Contributors Guide to get started!
- Auto-triage: New issues are automatically labeled
status: triageand added to Project 7 via.github/workflows/auto-triage.yml. - Milestone backfill: Run manually from the Actions tab using "Backfill milestones on issues". Start with dry_run=true to preview; set to false to apply.
- Project saved views: To create helpful saved views (Priority and Milestone) for Project 7, run:
- Make executable:
chmod +x scripts/setup_project_views.sh - Run with defaults:
OWNER=onnwee PROJECT_NUMBER=7 ./scripts/setup_project_views.sh
- Make executable: