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
This commit is contained in:
92
.env.example
92
.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
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user