Files
reflector/server/reflector/settings.py
Juan Diego García bc8338fa4f feat: Livekit - Selfhost video room solution (#946)
* feat: Livekit bare no recording nor pipeline

* feat: full livekit pipeline

* fix: caddy hatchet with livekit

* fix: caddy livekit

* fix: hatchet tls

* fix: agg to webm for no padding

* fix: reflector user id on participants and duration fix

* fix: better docs and internal review fixes

* fix: remove video files livekit
2026-04-07 11:55:16 -05:00

240 lines
8.3 KiB
Python

from pydantic.types import PositiveInt
from pydantic_settings import BaseSettings, SettingsConfigDict
from reflector.schemas.platform import DAILY_PLATFORM, Platform
from reflector.utils.string import NonEmptyString
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
ROOT_PATH: str = "/"
# WebRTC port range for ICE candidates (e.g. "50000-50100").
# When set, monkey-patches aioice to bind UDP sockets within this range,
# allowing Docker port mapping instead of network_mode: host.
WEBRTC_PORT_RANGE: str | None = None
# Host IP or hostname to advertise in ICE candidates instead of the
# container's internal IP. Use "host.docker.internal" in Docker with
# extra_hosts, or a specific LAN IP. Resolved at connection time.
WEBRTC_HOST: str | None = None
# CORS
UI_BASE_URL: str = "http://localhost:3000"
CORS_ORIGIN: str = "*"
CORS_ALLOW_CREDENTIALS: bool = False
# Database
DATABASE_URL: str = (
"postgresql+asyncpg://reflector:reflector@localhost:5432/reflector"
)
# local data directory
DATA_DIR: str = "./data"
# Audio Chunking
# backends: silero, frames
AUDIO_CHUNKER_BACKEND: str = "frames"
# HuggingFace token for gated models (pyannote diarization in --cpu mode)
HF_TOKEN: str | None = None
# Audio Transcription
# backends:
# - whisper: in-process model loading (no HTTP, runs in same process)
# - modal: HTTP API client (works with Modal.com OR self-hosted gpu/self_hosted/)
TRANSCRIPT_BACKEND: str = "whisper"
# Whisper model sizes for local transcription
# Options: "tiny", "base", "small", "medium", "large-v2"
WHISPER_CHUNK_MODEL: str = "tiny"
WHISPER_FILE_MODEL: str = "tiny"
TRANSCRIPT_URL: str | None = None
TRANSCRIPT_TIMEOUT: int = 90
TRANSCRIPT_FILE_TIMEOUT: int = (
540 # Below Hatchet TIMEOUT_HEAVY (600) to avoid timeout race
)
# Audio Transcription: modal backend
TRANSCRIPT_MODAL_API_KEY: str | None = None
# Audio transcription storage
TRANSCRIPT_STORAGE_BACKEND: str | None = None
# Storage configuration for AWS
TRANSCRIPT_STORAGE_AWS_BUCKET_NAME: str = "reflector-bucket"
TRANSCRIPT_STORAGE_AWS_REGION: str = "us-east-1"
TRANSCRIPT_STORAGE_AWS_ACCESS_KEY_ID: str | None = None
TRANSCRIPT_STORAGE_AWS_SECRET_ACCESS_KEY: str | None = None
TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL: str | None = None
# Platform-specific recording storage (follows {PREFIX}_STORAGE_AWS_{CREDENTIAL} pattern)
# Whereby storage configuration
WHEREBY_STORAGE_AWS_BUCKET_NAME: str | None = None
WHEREBY_STORAGE_AWS_REGION: str | None = None
WHEREBY_STORAGE_AWS_ACCESS_KEY_ID: str | None = None
WHEREBY_STORAGE_AWS_SECRET_ACCESS_KEY: str | None = None
# Daily.co storage configuration
DAILYCO_STORAGE_AWS_BUCKET_NAME: str | None = None
DAILYCO_STORAGE_AWS_REGION: str | None = None
DAILYCO_STORAGE_AWS_ROLE_ARN: str | None = None
# Worker credentials for reading/deleting from Daily's recording bucket
DAILYCO_STORAGE_AWS_ACCESS_KEY_ID: str | None = None
DAILYCO_STORAGE_AWS_SECRET_ACCESS_KEY: str | None = None
# Translate into the target language
TRANSLATION_BACKEND: str = "passthrough"
TRANSLATE_URL: str | None = None
TRANSLATE_TIMEOUT: int = 90
# Translation: modal backend
TRANSLATE_MODAL_API_KEY: str | None = None
# LLM
LLM_MODEL: str = "microsoft/phi-4"
LLM_URL: str | None = None
LLM_API_KEY: str | None = None
LLM_CONTEXT_WINDOW: int = 16000
LLM_REQUEST_TIMEOUT: float = 300.0 # HTTP request timeout for LLM calls (seconds)
LLM_PARSE_MAX_RETRIES: int = (
3 # Max retries for JSON/validation errors (total attempts = retries + 1)
)
LLM_STRUCTURED_RESPONSE_TIMEOUT: int = (
300 # Timeout in seconds for structured responses (5 minutes)
)
# Diarization
# backends: modal — HTTP API client, pyannote — in-process pyannote.audio
DIARIZATION_ENABLED: bool = True
DIARIZATION_BACKEND: str = "modal"
DIARIZATION_URL: str | None = None
DIARIZATION_FILE_TIMEOUT: int = 600
# Diarization: modal backend
DIARIZATION_MODAL_API_KEY: str | None = None
# Audio Padding
# backends:
# - pyav: in-process PyAV padding (no HTTP, runs in same process)
# - modal: HTTP API client (works with Modal.com OR self-hosted gpu/self_hosted/)
PADDING_BACKEND: str = "pyav"
PADDING_URL: str | None = None
PADDING_MODAL_API_KEY: str | None = None
# Audio Mixdown
# backends:
# - pyav: in-process PyAV mixdown (no HTTP, runs in same process)
# - modal: HTTP API client (works with Modal.com OR self-hosted gpu/self_hosted/)
MIXDOWN_BACKEND: str = "pyav"
MIXDOWN_URL: str | None = None
MIXDOWN_MODAL_API_KEY: str | None = None
# Sentry
SENTRY_DSN: str | None = None
# User authentication (none, jwt, password)
AUTH_BACKEND: str = "none"
# User authentication using JWT
AUTH_JWT_ALGORITHM: str = "RS256"
AUTH_JWT_PUBLIC_KEY: str | None = "authentik.monadical.com_public.pem"
AUTH_JWT_AUDIENCE: str | None = None
# User authentication using password (selfhosted)
ADMIN_EMAIL: str | None = None
ADMIN_PASSWORD_HASH: str | None = None
PUBLIC_MODE: bool = False
PUBLIC_DATA_RETENTION_DAYS: PositiveInt = 7
# Min transcript length to generate topic + summary
MIN_TRANSCRIPT_LENGTH: int = 750
# Celery
CELERY_BROKER_URL: str = "redis://localhost:6379/1"
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/1"
# Redis
REDIS_HOST: str = "localhost"
REDIS_PORT: int = 6379
REDIS_CACHE_DB: int = 2
# Secret key
SECRET_KEY: str = "changeme-f02f86fd8b3e4fd892c6043e5a298e21"
# Current hosting/domain
BASE_URL: str = "http://localhost:1250"
# Profiling
PROFILING: bool = False
# Healthcheck
HEALTHCHECK_URL: str | None = None
# Whereby integration
WHEREBY_API_URL: str = "https://api.whereby.dev/v1"
WHEREBY_API_KEY: NonEmptyString | None = None
WHEREBY_WEBHOOK_SECRET: str | None = None
AWS_PROCESS_RECORDING_QUEUE_URL: str | None = None
SQS_POLLING_TIMEOUT_SECONDS: int = 60
CELERY_BEAT_POLL_INTERVAL: int = (
0 # 0 = use individual defaults; set e.g. 300 for 5-min polling
)
# Daily.co integration
DAILY_API_URL: str = "https://api.daily.co/v1"
DAILY_API_KEY: str | None = None
DAILY_WEBHOOK_SECRET: str | None = None
DAILY_SUBDOMAIN: str | None = None
DAILY_WEBHOOK_UUID: str | None = (
None # Webhook UUID for this environment. Not used by production code
)
# LiveKit integration (self-hosted open-source video platform)
LIVEKIT_URL: str | None = (
None # e.g. ws://livekit:7880 (internal) or wss://livekit.example.com
)
LIVEKIT_API_KEY: str | None = None
LIVEKIT_API_SECRET: str | None = None
LIVEKIT_WEBHOOK_SECRET: str | None = None # Defaults to API_SECRET if not set
# LiveKit egress S3 storage (Track Egress writes per-participant audio here)
LIVEKIT_STORAGE_AWS_BUCKET_NAME: str | None = None
LIVEKIT_STORAGE_AWS_REGION: str | None = None
LIVEKIT_STORAGE_AWS_ACCESS_KEY_ID: str | None = None
LIVEKIT_STORAGE_AWS_SECRET_ACCESS_KEY: str | None = None
LIVEKIT_STORAGE_AWS_ENDPOINT_URL: str | None = None # For Garage/MinIO
# Public URL for LiveKit (used in frontend room_url, e.g. wss://livekit.example.com)
LIVEKIT_PUBLIC_URL: str | None = None
# Platform Configuration
DEFAULT_VIDEO_PLATFORM: Platform = DAILY_PLATFORM
# Zulip integration
ZULIP_REALM: str | None = None
ZULIP_API_KEY: str | None = None
ZULIP_BOT_EMAIL: str | None = None
ZULIP_DAG_STREAM: str | None = None
ZULIP_DAG_TOPIC: str | None = None
# Email / SMTP integration (for transcript email notifications)
SMTP_HOST: str | None = None
SMTP_PORT: int = 587
SMTP_USERNAME: str | None = None
SMTP_PASSWORD: str | None = None
SMTP_FROM_EMAIL: str | None = None
SMTP_USE_TLS: bool = True
# Hatchet workflow orchestration (always enabled for multitrack processing)
HATCHET_CLIENT_TOKEN: str | None = None
HATCHET_CLIENT_TLS_STRATEGY: str = "none" # none, tls, mtls
HATCHET_DEBUG: bool = False
settings = Settings()