video-accessibility/docker-compose.yml
Vadym Samoilenko 2f4925353a feat(pause-insert): adaptive buffer, forward-snap, timeline drag + share link fix
Backend (Phase A):
- A1: Adaptive silence buffer — natural_gap_ms persisted per cue; renderer computes
  per-cue silence_before/silence_after instead of fixed 500ms; per-cue silence files
- A2: Forward-preferred snap — snap_pause_point prefers boundaries up to 4s ahead
  over boundaries within 1.5s behind, reducing mid-scene cuts
- A3: Min-gap validation — pause points with < 200ms gap trigger forward search
  to the next acceptable gap
- natural_gap_ms added to PausePointData model and api.ts type
- New config fields: whisper_snap_forward_window, whisper_snap_backward_window,
  ad_silence_buffer_default, ad_silence_buffer_min_after, ad_min_acceptable_gap
- Tests: test_whisper_snap.py (13 tests), test_video_renderer_buffers.py

Frontend (Phase B):
- B1: Drag pause-point markers — pointer state machine with 3px move threshold,
  clamp to min/max bounds, click-without-move still opens PausePointEditor
- B2: Drag freeze blocks — orange blocks translate with linked pause point
- B3: Time tooltip visible during drag, hidden on release
- Tests: TimelinePreview.drag.test.tsx (10 tests)

Fixes:
- Share link pointed to ai-sandbox.oliver.solutions — added app_url to Settings
  with correct optical-dev.oliver.solutions default; share_url now configurable
  via APP_URL env var
- Removed all ai-sandbox.oliver.solutions references from docker-compose,
  apache config, docs, and scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:09:09 +01:00

562 lines
20 KiB
YAML

# =============================================================================
# Docker Compose Configuration for Accessible Video Processing Platform
# =============================================================================
# Services:
# - api: FastAPI + Gunicorn REST API
# - worker: Celery worker for background processing (default,ingest,notify,render)
# - tts-worker: Dedicated TTS worker (tts queue, concurrency=${TTS_WORKER_CONCURRENCY:-2})
# - ffmpeg-worker: Dedicated FFmpeg worker (ffmpeg queue, concurrency=1)
# - whisper-worker: Dedicated Whisper worker (whisper queue, concurrency=1)
# - mongodb: MongoDB database
# - redis: Redis for Celery broker and cache
# =============================================================================
version: '3.8'
services:
# ---------------------------------------------------------------------------
# MongoDB Database
# ---------------------------------------------------------------------------
mongodb:
image: mongo:7.0
container_name: accessible-video-mongodb
restart: unless-stopped
command: ["mongod", "--config", "/etc/mongod.conf", "--quiet"]
environment:
MONGO_INITDB_DATABASE: ${MONGODB_DB:-accessible_video}
volumes:
- mongodb-data:/data/db
- mongodb-config:/data/configdb
- ./config/mongod.conf:/etc/mongod.conf:ro
networks:
- accessible-video-network
healthcheck:
# TCP port check avoids mongosh connection metadata spam in logs
test: ["CMD-SHELL", "timeout 5 bash -c '</dev/tcp/localhost/27017' || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# Redis Cache and Message Broker
# ---------------------------------------------------------------------------
redis:
image: redis:7-alpine
container_name: accessible-video-redis
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- accessible-video-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# FastAPI Backend API
# ---------------------------------------------------------------------------
api:
build:
context: ./backend
dockerfile: Dockerfile
target: api
container_name: accessible-video-api
restart: unless-stopped
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8012:8000"
environment:
# App configuration
APP_ENV: ${APP_ENV:-dev}
API_BASE_URL: ${API_BASE_URL:-http://localhost:8000}
# Auth
JWT_SECRET: ${JWT_SECRET}
JWT_ALG: ${JWT_ALG:-HS256}
JWT_ACCESS_TTL_MIN: ${JWT_ACCESS_TTL_MIN:-240}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-7}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-optical-dev.oliver.solutions}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
COOKIE_SAMESITE: ${COOKIE_SAMESITE:-Lax}
# Database
MONGODB_URI: mongodb://mongodb:27017/${MONGODB_DB:-accessible_video}
MONGODB_DB: ${MONGODB_DB:-accessible_video}
# Redis
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
# GCP
GCP_PROJECT_ID: ${GCP_PROJECT_ID}
GCS_BUCKET: ${GCS_BUCKET:-accessible-video}
GOOGLE_APPLICATION_CREDENTIALS: /secrets/gcp-credentials.json
# AI Services
GEMINI_API_KEY: ${GEMINI_API_KEY}
TRANSLATE_API_KEY: ${TRANSLATE_API_KEY:-}
ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}
# Email
SENDGRID_API_KEY: ${SENDGRID_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-noreply@optical-dev.oliver.solutions}
CLIENT_BASE_URL: ${CLIENT_BASE_URL:-https://optical-dev.oliver.solutions/video-accessibility}
# Microsoft Authentication
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_AUTHORITY: ${AZURE_AUTHORITY:-}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
# CORS
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:6001,http://localhost:5173,http://localhost:3000}
# Observability
SENTRY_DSN: ${SENTRY_DSN:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
# AI Cost Tracker
COST_TRACKER_BASE_URL: ${COST_TRACKER_BASE_URL:-}
COST_TRACKER_API_KEY: ${COST_TRACKER_API_KEY:-}
COST_TRACKER_SOURCE_APP: ${COST_TRACKER_SOURCE_APP:-video-accessibility}
COST_TRACKER_ENABLED: ${COST_TRACKER_ENABLED:-true}
# Pipeline dispatch mode
USE_CELERY_FALLBACK: ${USE_CELERY_FALLBACK:-false}
volumes:
- ./secrets:/secrets:ro
- api-logs:/app/logs
networks:
- accessible-video-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# Celery Worker for Background Processing (excludes ffmpeg and whisper queues)
# ---------------------------------------------------------------------------
worker:
build:
context: ./backend
dockerfile: Dockerfile
target: worker
container_name: accessible-video-worker
restart: unless-stopped
user: root
command: >
sh -c "chown -R app:app /shared-tmp &&
su app -c 'celery -A celery_worker worker -Q default,ingest,notify,render --loglevel=info --concurrency=${WORKER_CONCURRENCY:-8}'"
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Shared temp directory for ffmpeg operations
TMPDIR: /shared-tmp
# App configuration
APP_ENV: ${APP_ENV:-dev}
# Cloud Run Service URLs (set these to enable Cloud Run autoscaling)
# When set, CPU-intensive operations are offloaded to Cloud Run
WHISPER_SERVICE_URL: ${WHISPER_SERVICE_URL:-}
FFMPEG_SERVICE_URL: ${FFMPEG_SERVICE_URL:-}
USE_CELERY_FALLBACK: ${USE_CELERY_FALLBACK:-false}
# Auth (required by Settings class even though worker doesn't use it)
JWT_SECRET: ${JWT_SECRET}
JWT_ALG: ${JWT_ALG:-HS256}
JWT_ACCESS_TTL_MIN: ${JWT_ACCESS_TTL_MIN:-240}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-7}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-optical-dev.oliver.solutions}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
COOKIE_SAMESITE: ${COOKIE_SAMESITE:-Lax}
# Database
MONGODB_URI: mongodb://mongodb:27017/${MONGODB_DB:-accessible_video}
MONGODB_DB: ${MONGODB_DB:-accessible_video}
# Redis
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
# GCP
GCP_PROJECT_ID: ${GCP_PROJECT_ID}
GCS_BUCKET: ${GCS_BUCKET:-accessible-video}
GOOGLE_APPLICATION_CREDENTIALS: /secrets/gcp-credentials.json
# AI Services
GEMINI_API_KEY: ${GEMINI_API_KEY}
TRANSLATE_API_KEY: ${TRANSLATE_API_KEY:-}
ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}
GOOGLE_TTS_CREDENTIALS: /secrets/gcp-credentials.json
# Email
SENDGRID_API_KEY: ${SENDGRID_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-noreply@optical-dev.oliver.solutions}
CLIENT_BASE_URL: ${CLIENT_BASE_URL:-https://optical-dev.oliver.solutions/video-accessibility}
# Microsoft Authentication
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_AUTHORITY: ${AZURE_AUTHORITY:-}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
# CORS
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:6001,http://localhost:5173,http://localhost:3000}
# Observability
SENTRY_DSN: ${SENTRY_DSN:-}
# AI Cost Tracker
COST_TRACKER_BASE_URL: ${COST_TRACKER_BASE_URL:-}
COST_TRACKER_API_KEY: ${COST_TRACKER_API_KEY:-}
COST_TRACKER_SOURCE_APP: ${COST_TRACKER_SOURCE_APP:-video-accessibility}
COST_TRACKER_ENABLED: ${COST_TRACKER_ENABLED:-true}
volumes:
- ./secrets:/secrets:ro
- worker-logs:/app/logs
- shared-tmp:/shared-tmp
networks:
- accessible-video-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# TTS Worker - Dedicated worker for TTS synthesis
# ---------------------------------------------------------------------------
tts-worker:
build:
context: ./backend
dockerfile: Dockerfile
target: worker
container_name: accessible-video-tts-worker
restart: unless-stopped
user: root
command: >
sh -c "chown -R app:app /shared-tmp 2>/dev/null || true &&
su app -c 'celery -A celery_worker worker -Q tts --loglevel=info --concurrency=${TTS_WORKER_CONCURRENCY:-2}'"
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Shared temp directory
TMPDIR: /shared-tmp
# App configuration
APP_ENV: ${APP_ENV:-dev}
# Auth (required by Settings class even though worker doesn't use it)
JWT_SECRET: ${JWT_SECRET}
JWT_ALG: ${JWT_ALG:-HS256}
JWT_ACCESS_TTL_MIN: ${JWT_ACCESS_TTL_MIN:-240}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-7}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-optical-dev.oliver.solutions}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
COOKIE_SAMESITE: ${COOKIE_SAMESITE:-Lax}
# Database
MONGODB_URI: mongodb://mongodb:27017/${MONGODB_DB:-accessible_video}
MONGODB_DB: ${MONGODB_DB:-accessible_video}
# Redis
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
# GCP
GCP_PROJECT_ID: ${GCP_PROJECT_ID}
GCS_BUCKET: ${GCS_BUCKET:-accessible-video}
GOOGLE_APPLICATION_CREDENTIALS: /secrets/gcp-credentials.json
# AI Services
GEMINI_API_KEY: ${GEMINI_API_KEY}
TRANSLATE_API_KEY: ${TRANSLATE_API_KEY:-}
ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}
GOOGLE_TTS_CREDENTIALS: /secrets/gcp-credentials.json
# Email
SENDGRID_API_KEY: ${SENDGRID_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-noreply@optical-dev.oliver.solutions}
CLIENT_BASE_URL: ${CLIENT_BASE_URL:-https://optical-dev.oliver.solutions/video-accessibility}
# Microsoft Authentication
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_AUTHORITY: ${AZURE_AUTHORITY:-}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
# CORS
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:6001,http://localhost:5173,http://localhost:3000}
# Observability
SENTRY_DSN: ${SENTRY_DSN:-}
# AI Cost Tracker
COST_TRACKER_BASE_URL: ${COST_TRACKER_BASE_URL:-}
COST_TRACKER_API_KEY: ${COST_TRACKER_API_KEY:-}
COST_TRACKER_SOURCE_APP: ${COST_TRACKER_SOURCE_APP:-video-accessibility}
COST_TRACKER_ENABLED: ${COST_TRACKER_ENABLED:-true}
volumes:
- ./secrets:/secrets:ro
- tts-worker-logs:/app/logs
- shared-tmp:/shared-tmp
networks:
- accessible-video-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# FFmpeg Worker - Dedicated worker for video encoding
# Concurrency: 1 for local mode (CPU bound), 20 for Cloud Run mode (HTTP calls)
# ---------------------------------------------------------------------------
ffmpeg-worker:
build:
context: ./backend
dockerfile: Dockerfile
target: worker
container_name: accessible-video-ffmpeg-worker
restart: unless-stopped
user: root
command: >
sh -c "chown -R app:app /shared-tmp 2>/dev/null || true &&
su app -c 'celery -A celery_worker worker -Q ffmpeg --loglevel=info --concurrency=${FFMPEG_WORKER_CONCURRENCY:-1}'"
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Shared temp directory for ffmpeg operations
TMPDIR: /shared-tmp
# App configuration
APP_ENV: ${APP_ENV:-dev}
# Cloud Run Service URLs (set these to enable Cloud Run autoscaling)
# When set, FFmpeg operations are offloaded to Cloud Run (HTTP calls)
# This allows higher concurrency since workers just wait for HTTP responses
FFMPEG_SERVICE_URL: ${FFMPEG_SERVICE_URL:-}
# Auth (required by Settings class even though worker doesn't use it)
JWT_SECRET: ${JWT_SECRET}
JWT_ALG: ${JWT_ALG:-HS256}
JWT_ACCESS_TTL_MIN: ${JWT_ACCESS_TTL_MIN:-240}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-7}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-optical-dev.oliver.solutions}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
COOKIE_SAMESITE: ${COOKIE_SAMESITE:-Lax}
# Database
MONGODB_URI: mongodb://mongodb:27017/${MONGODB_DB:-accessible_video}
MONGODB_DB: ${MONGODB_DB:-accessible_video}
# Redis
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
# GCP
GCP_PROJECT_ID: ${GCP_PROJECT_ID}
GCS_BUCKET: ${GCS_BUCKET:-accessible-video}
GOOGLE_APPLICATION_CREDENTIALS: /secrets/gcp-credentials.json
# AI Services
GEMINI_API_KEY: ${GEMINI_API_KEY}
TRANSLATE_API_KEY: ${TRANSLATE_API_KEY:-}
ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}
GOOGLE_TTS_CREDENTIALS: /secrets/gcp-credentials.json
# Email
SENDGRID_API_KEY: ${SENDGRID_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-noreply@optical-dev.oliver.solutions}
CLIENT_BASE_URL: ${CLIENT_BASE_URL:-https://optical-dev.oliver.solutions/video-accessibility}
# Microsoft Authentication
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_AUTHORITY: ${AZURE_AUTHORITY:-}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
# CORS
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:6001,http://localhost:5173,http://localhost:3000}
# Observability
SENTRY_DSN: ${SENTRY_DSN:-}
# AI Cost Tracker
COST_TRACKER_BASE_URL: ${COST_TRACKER_BASE_URL:-}
COST_TRACKER_API_KEY: ${COST_TRACKER_API_KEY:-}
COST_TRACKER_SOURCE_APP: ${COST_TRACKER_SOURCE_APP:-video-accessibility}
COST_TRACKER_ENABLED: ${COST_TRACKER_ENABLED:-true}
volumes:
- ./secrets:/secrets:ro
- ffmpeg-worker-logs:/app/logs
- shared-tmp:/shared-tmp
networks:
- accessible-video-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ---------------------------------------------------------------------------
# Whisper Worker - Dedicated worker for Whisper transcription
# Concurrency: 1 for local mode (RAM bound ~4-6GB), 10 for Cloud Run mode (HTTP calls)
# Used for refining AD pause points with word-level speech analysis
# ---------------------------------------------------------------------------
whisper-worker:
build:
context: ./backend
dockerfile: Dockerfile
target: whisper-worker
container_name: accessible-video-whisper-worker
restart: unless-stopped
user: root
command: >
sh -c "chown -R app:app /shared-tmp 2>/dev/null || true &&
su app -c 'celery -A celery_worker worker -Q whisper --loglevel=info --concurrency=${WHISPER_WORKER_CONCURRENCY:-1} --max-tasks-per-child=50'"
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Shared temp directory for audio processing
TMPDIR: /shared-tmp
# App configuration
APP_ENV: ${APP_ENV:-dev}
# Cloud Run Service URL (set to enable Cloud Run autoscaling)
# When set, Whisper transcription is offloaded to Cloud Run (HTTP calls)
# This allows higher concurrency since workers just wait for HTTP responses
WHISPER_SERVICE_URL: ${WHISPER_SERVICE_URL:-}
# Whisper Configuration (for local mode only)
WHISPER_MODEL: ${WHISPER_MODEL:-medium}
# Auth (required by Settings class even though worker doesn't use it)
JWT_SECRET: ${JWT_SECRET}
JWT_ALG: ${JWT_ALG:-HS256}
JWT_ACCESS_TTL_MIN: ${JWT_ACCESS_TTL_MIN:-240}
JWT_REFRESH_TTL_DAYS: ${JWT_REFRESH_TTL_DAYS:-7}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-optical-dev.oliver.solutions}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
COOKIE_SAMESITE: ${COOKIE_SAMESITE:-Lax}
# Database
MONGODB_URI: mongodb://mongodb:27017/${MONGODB_DB:-accessible_video}
MONGODB_DB: ${MONGODB_DB:-accessible_video}
# Redis
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
# GCP
GCP_PROJECT_ID: ${GCP_PROJECT_ID}
GCS_BUCKET: ${GCS_BUCKET:-accessible-video}
GOOGLE_APPLICATION_CREDENTIALS: /secrets/gcp-credentials.json
# AI Services
GEMINI_API_KEY: ${GEMINI_API_KEY}
TRANSLATE_API_KEY: ${TRANSLATE_API_KEY:-}
ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}
GOOGLE_TTS_CREDENTIALS: /secrets/gcp-credentials.json
# Email
SENDGRID_API_KEY: ${SENDGRID_API_KEY:-}
EMAIL_FROM: ${EMAIL_FROM:-noreply@optical-dev.oliver.solutions}
CLIENT_BASE_URL: ${CLIENT_BASE_URL:-https://optical-dev.oliver.solutions/video-accessibility}
# Microsoft Authentication
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_AUTHORITY: ${AZURE_AUTHORITY:-}
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-}
# CORS
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:6001,http://localhost:5173,http://localhost:3000}
# Observability
SENTRY_DSN: ${SENTRY_DSN:-}
# AI Cost Tracker
COST_TRACKER_BASE_URL: ${COST_TRACKER_BASE_URL:-}
COST_TRACKER_API_KEY: ${COST_TRACKER_API_KEY:-}
COST_TRACKER_SOURCE_APP: ${COST_TRACKER_SOURCE_APP:-video-accessibility}
COST_TRACKER_ENABLED: ${COST_TRACKER_ENABLED:-true}
volumes:
- ./secrets:/secrets:ro
- whisper-worker-logs:/app/logs
- shared-tmp:/shared-tmp
networks:
- accessible-video-network
# Memory limit to prevent OOM (Whisper large-v3 model uses ~4-6GB)
deploy:
resources:
limits:
memory: 8G
reservations:
memory: 4G
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# =============================================================================
# Networks
# =============================================================================
networks:
accessible-video-network:
driver: bridge
name: accessible-video-network
# =============================================================================
# Volumes
# =============================================================================
volumes:
mongodb-data:
name: accessible-video-mongodb-data
mongodb-config:
name: accessible-video-mongodb-config
redis-data:
name: accessible-video-redis-data
api-logs:
name: accessible-video-api-logs
worker-logs:
name: accessible-video-worker-logs
ffmpeg-worker-logs:
name: accessible-video-ffmpeg-worker-logs
tts-worker-logs:
name: accessible-video-tts-worker-logs
whisper-worker-logs:
name: accessible-video-whisper-worker-logs
shared-tmp:
name: accessible-video-shared-tmp