From 9594c0b8ab6e67ac88c88517869bea8e35e57061 Mon Sep 17 00:00:00 2001 From: Dasemu Date: Fri, 16 Jan 2026 15:11:02 +0100 Subject: [PATCH] refactor(config): simplify to database-backed settings - Reduce .env.example to only DATABASE_URL - Simplify backend/config.py to only read database connection - All other settings now managed via database and Web UI --- .env.example | 92 +++-------------------- backend/config.py | 183 ++++++---------------------------------------- 2 files changed, 33 insertions(+), 242 deletions(-) diff --git a/.env.example b/.env.example index 274181f..f0537e3 100644 --- a/.env.example +++ b/.env.example @@ -1,90 +1,20 @@ # ============================================ # TranscriptorIO Configuration # ============================================ +# +# IMPORTANT: Most configuration is now stored in the database +# and managed through the Web UI Settings page. +# +# Only DATABASE_URL is required in this file. +# Run the server and complete the Setup Wizard for initial configuration. +# -# === Application Mode === -# Options: standalone, provider, or standalone,provider (hybrid mode) -TRANSCRIPTARR_MODE=standalone - -# === Database Configuration === -# SQLite (default - no additional driver needed) +# === Database Configuration (REQUIRED) === +# SQLite (default - good for single-user, no additional driver needed) DATABASE_URL=sqlite:///./transcriptarr.db -# PostgreSQL example (requires psycopg2-binary) +# PostgreSQL (recommended for production, requires psycopg2-binary) # DATABASE_URL=postgresql://user:password@localhost:5432/transcriptarr -# MariaDB/MySQL example (requires pymysql) +# MariaDB/MySQL (requires pymysql) # DATABASE_URL=mariadb+pymysql://user:password@localhost:3306/transcriptarr - -# === Worker Configuration === -CONCURRENT_TRANSCRIPTIONS=2 -WHISPER_THREADS=4 -TRANSCRIBE_DEVICE=cpu -CLEAR_VRAM_ON_COMPLETE=True - -# === Whisper Model Configuration === -# Options: tiny, base, small, medium, large-v3, large-v3-turbo, etc. -WHISPER_MODEL=medium -MODEL_PATH=./models -COMPUTE_TYPE=auto - -# === Standalone Mode Configuration === -# Pipe-separated paths to scan -LIBRARY_PATHS=/media/anime|/media/movies -AUTO_SCAN_ENABLED=False -SCAN_INTERVAL_MINUTES=30 - -# Filter rules for standalone mode -REQUIRED_AUDIO_LANGUAGE=ja -REQUIRED_MISSING_SUBTITLE=spa -SKIP_IF_SUBTITLE_EXISTS=True - -# === Provider Mode Configuration === -BAZARR_URL=http://bazarr:6767 -BAZARR_API_KEY=your_api_key_here -PROVIDER_TIMEOUT_SECONDS=600 -PROVIDER_CALLBACK_ENABLED=True -PROVIDER_POLLING_INTERVAL=30 - -# === API Configuration === -WEBHOOK_PORT=9000 -API_HOST=0.0.0.0 -DEBUG=True - -# === Transcription Settings === -# Options: transcribe, translate -TRANSCRIBE_OR_TRANSLATE=transcribe -SUBTITLE_LANGUAGE_NAME= -# Options: ISO_639_1, ISO_639_2_T, ISO_639_2_B, NAME, NATIVE -SUBTITLE_LANGUAGE_NAMING_TYPE=ISO_639_2_B -WORD_LEVEL_HIGHLIGHT=False -CUSTOM_REGROUP=cm_sl=84_sl=42++++++1 - -# === Skip Configuration === -SKIP_IF_EXTERNAL_SUBTITLES_EXIST=False -SKIP_IF_TARGET_SUBTITLES_EXIST=True -SKIP_IF_INTERNAL_SUBTITLES_LANGUAGE=eng -# Pipe-separated language codes -SKIP_SUBTITLE_LANGUAGES= -SKIP_IF_AUDIO_LANGUAGES= -SKIP_UNKNOWN_LANGUAGE=False -SKIP_ONLY_SUBGEN_SUBTITLES=False - -# === Advanced Settings === -FORCE_DETECTED_LANGUAGE_TO= -DETECT_LANGUAGE_LENGTH=30 -DETECT_LANGUAGE_OFFSET=0 -SHOULD_WHISPER_DETECT_AUDIO_LANGUAGE=False -# Pipe-separated list in order of preference -PREFERRED_AUDIO_LANGUAGES=eng - -# === Path Mapping === -USE_PATH_MAPPING=False -PATH_MAPPING_FROM=/tv -PATH_MAPPING_TO=/Volumes/TV - -# === Legacy SubGen Compatibility === -SHOW_IN_SUBNAME_SUBGEN=True -SHOW_IN_SUBNAME_MODEL=True -APPEND=False -LRC_FOR_AUDIO_FILES=True \ No newline at end of file diff --git a/backend/config.py b/backend/config.py index 5b8017b..26e2326 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,18 +1,17 @@ -"""Configuration management for TranscriptorIO.""" -import os +"""Configuration management for TranscriptorIO. + +Most configuration is now stored in the database and managed through the +Settings service. Only DATABASE_URL is loaded from environment variables. + +For runtime configuration, use: + from backend.core.settings_service import settings_service + value = settings_service.get("setting_key", default_value) +""" from enum import Enum -from typing import Optional, List from pydantic_settings import BaseSettings from pydantic import Field, field_validator -class OperationMode(str, Enum): - """Operation modes for TranscriptorIO.""" - STANDALONE = "standalone" - PROVIDER = "provider" - HYBRID = "standalone,provider" - - class DatabaseType(str, Enum): """Supported database backends.""" SQLITE = "sqlite" @@ -22,131 +21,29 @@ class DatabaseType(str, Enum): class Settings(BaseSettings): - """Application settings loaded from environment variables.""" + """ + Application settings loaded from environment variables. - # === Application Mode === - transcriptarr_mode: str = Field( - default="standalone", - description="Operation mode: standalone, provider, or standalone,provider" - ) + Only DATABASE_URL is required. All other configuration is stored + in the database and managed through the Settings API/UI. + """ - # === Database Configuration === + # === Database Configuration (REQUIRED) === database_url: str = Field( default="sqlite:///./transcriptarr.db", - description="Database connection URL. Examples:\n" - " SQLite: sqlite:///./transcriptarr.db\n" - " PostgreSQL: postgresql://user:pass@localhost/transcriptarr\n" - " MariaDB: mariadb+pymysql://user:pass@localhost/transcriptarr" + description="Database connection URL" ) - # === Worker Configuration === - concurrent_transcriptions: int = Field(default=2, ge=1, le=10) - whisper_threads: int = Field(default=4, ge=1, le=32) - transcribe_device: str = Field(default="cpu", pattern="^(cpu|gpu|cuda)$") - clear_vram_on_complete: bool = Field(default=True) - - # === Whisper Model Configuration === - whisper_model: str = Field( - default="medium", - description="Whisper model: tiny, base, small, medium, large-v3, etc." - ) - model_path: str = Field(default="./models") - compute_type: str = Field(default="auto") - - # === Standalone Mode Configuration === - library_paths: Optional[str] = Field( - default=None, - description="Pipe-separated paths to scan: /media/anime|/media/movies" - ) - auto_scan_enabled: bool = Field(default=False) - scan_interval_minutes: int = Field(default=30, ge=1) - - required_audio_language: Optional[str] = Field( - default=None, - description="Only process files with this audio language (ISO 639-2)" - ) - required_missing_subtitle: Optional[str] = Field( - default=None, - description="Only process if this subtitle language is missing (ISO 639-2)" - ) - skip_if_subtitle_exists: bool = Field(default=True) - - # === Provider Mode Configuration === - bazarr_url: Optional[str] = Field(default=None) - bazarr_api_key: Optional[str] = Field(default=None) - provider_timeout_seconds: int = Field(default=600, ge=60) - provider_callback_enabled: bool = Field(default=True) - provider_polling_interval: int = Field(default=30, ge=10) - - # === API Configuration === - webhook_port: int = Field(default=9000, ge=1024, le=65535) - api_host: str = Field(default="0.0.0.0") - debug: bool = Field(default=True) - - # === Transcription Settings === - transcribe_or_translate: str = Field( - default="transcribe", - pattern="^(transcribe|translate)$" - ) - subtitle_language_name: str = Field(default="") - subtitle_language_naming_type: str = Field( - default="ISO_639_2_B", - description="Naming format: ISO_639_1, ISO_639_2_T, ISO_639_2_B, NAME, NATIVE" - ) - word_level_highlight: bool = Field(default=False) - custom_regroup: str = Field(default="cm_sl=84_sl=42++++++1") - - # === Skip Configuration === - skip_if_external_subtitles_exist: bool = Field(default=False) - skip_if_target_subtitles_exist: bool = Field(default=True) - skip_if_internal_subtitles_language: Optional[str] = Field(default="eng") - skip_subtitle_languages: Optional[str] = Field( - default=None, - description="Pipe-separated language codes to skip: eng|spa" - ) - skip_if_audio_languages: Optional[str] = Field( - default=None, - description="Skip if audio track is in these languages: eng|spa" - ) - skip_unknown_language: bool = Field(default=False) - skip_only_subgen_subtitles: bool = Field(default=False) - - # === Advanced Settings === - force_detected_language_to: Optional[str] = Field(default=None) - detect_language_length: int = Field(default=30, ge=5) - detect_language_offset: int = Field(default=0, ge=0) - should_whisper_detect_audio_language: bool = Field(default=False) - - preferred_audio_languages: str = Field( - default="eng", - description="Pipe-separated list in order of preference: eng|jpn" - ) - - # === Path Mapping === - use_path_mapping: bool = Field(default=False) - path_mapping_from: str = Field(default="/tv") - path_mapping_to: str = Field(default="/Volumes/TV") - - # === Legacy SubGen Compatibility === - show_in_subname_subgen: bool = Field(default=True) - show_in_subname_model: bool = Field(default=True) - append: bool = Field(default=False) - lrc_for_audio_files: bool = Field(default=True) - - @field_validator("transcriptarr_mode") - @classmethod - def validate_mode(cls, v: str) -> str: - """Validate operation mode.""" - valid_modes = {"standalone", "provider", "standalone,provider"} - if v not in valid_modes: - raise ValueError(f"Invalid mode: {v}. Must be one of: {valid_modes}") - return v - @field_validator("database_url") @classmethod def validate_database_url(cls, v: str) -> str: """Validate database URL format.""" - valid_prefixes = ("sqlite://", "postgresql://", "mariadb+pymysql://", "mysql+pymysql://") + valid_prefixes = ( + "sqlite://", + "postgresql://", + "mariadb+pymysql://", + "mysql+pymysql://" + ) if not any(v.startswith(prefix) for prefix in valid_prefixes): raise ValueError( f"Invalid database URL. Must start with one of: {valid_prefixes}" @@ -167,42 +64,6 @@ class Settings(BaseSettings): else: raise ValueError(f"Unknown database type in URL: {self.database_url}") - @property - def is_standalone_mode(self) -> bool: - """Check if standalone mode is enabled.""" - return "standalone" in self.transcriptarr_mode - - @property - def is_provider_mode(self) -> bool: - """Check if provider mode is enabled.""" - return "provider" in self.transcriptarr_mode - - @property - def library_paths_list(self) -> List[str]: - """Get library paths as a list.""" - if not self.library_paths: - return [] - return [p.strip() for p in self.library_paths.split("|") if p.strip()] - - @property - def skip_subtitle_languages_list(self) -> List[str]: - """Get skip subtitle languages as a list.""" - if not self.skip_subtitle_languages: - return [] - return [lang.strip() for lang in self.skip_subtitle_languages.split("|") if lang.strip()] - - @property - def skip_audio_languages_list(self) -> List[str]: - """Get skip audio languages as a list.""" - if not self.skip_if_audio_languages: - return [] - return [lang.strip() for lang in self.skip_if_audio_languages.split("|") if lang.strip()] - - @property - def preferred_audio_languages_list(self) -> List[str]: - """Get preferred audio languages as a list.""" - return [lang.strip() for lang in self.preferred_audio_languages.split("|") if lang.strip()] - class Config: """Pydantic configuration.""" env_file = ".env"