Files
jellyfin-client/src/config.py
T

160 lines
5.3 KiB
Python

from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional
import yaml
@dataclass
class JellyfinConfig:
server_url: str = "http://127.0.0.1:8096"
username: str = ""
password: str = ""
token: Optional[str] = None
client_name: str = "Jellyfin HTPC Client"
device_name: str = "HTPC"
device_id: str = "htpc-default-01"
@dataclass
class PlayerConfig:
mpv_path: str = "mpv"
ipc_socket: str = "/tmp/mpv-jellyfin.sock"
hwdec: str = "auto"
vo: str = "gpu"
fullscreen: bool = True
audio_device: str = "auto"
extra_args: List[str] = field(default_factory=lambda: [
"--keep-open=yes",
"--force-window=immediate",
"--osd-font=sans-serif",
"--osd-font-size=28",
])
@dataclass
class KodiRpcConfig:
enabled: bool = True
http_port: int = 8080
ws_port: int = 9090
event_port: int = 9777
@dataclass
class WebApiConfig:
enabled: bool = True
host: str = "0.0.0.0"
port: int = 8097
@dataclass
class YoutubeConfig:
max_height: int = 1080
fetch_metadata: bool = True
@dataclass
class AppConfig:
jellyfin: JellyfinConfig = field(default_factory=JellyfinConfig)
player: PlayerConfig = field(default_factory=PlayerConfig)
kodi_rpc: KodiRpcConfig = field(default_factory=KodiRpcConfig)
web_api: WebApiConfig = field(default_factory=WebApiConfig)
youtube: YoutubeConfig = field(default_factory=YoutubeConfig)
@classmethod
def load(cls, config_path: Optional[str | Path] = None) -> AppConfig:
paths_to_check = []
if config_path:
paths_to_check.append(Path(config_path))
if env_path := os.environ.get("JELLYFIN_CLIENT_CONFIG"):
paths_to_check.append(Path(env_path))
paths_to_check.extend([
Path.cwd() / "config.yaml",
Path.home() / ".config" / "jellyfin-client" / "config.yaml",
Path("/etc/jellyfin-client/config.yaml"),
])
data: Dict[str, Any] = {}
found_path = None
for p in paths_to_check:
if p.is_file():
found_path = p
with open(p, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) or {}
break
cfg = cls()
if not data:
return cfg
if "jellyfin" in data:
jf = data["jellyfin"]
cfg.jellyfin = JellyfinConfig(
server_url=jf.get("server_url", cfg.jellyfin.server_url).rstrip("/"),
username=jf.get("username", cfg.jellyfin.username),
password=jf.get("password", cfg.jellyfin.password),
token=jf.get("token", cfg.jellyfin.token),
client_name=jf.get("client_name", cfg.jellyfin.client_name),
device_name=jf.get("device_name", cfg.jellyfin.device_name),
device_id=jf.get("device_id", cfg.jellyfin.device_id),
)
if "player" in data:
pl = data["player"]
cfg.player = PlayerConfig(
mpv_path=pl.get("mpv_path", cfg.player.mpv_path),
ipc_socket=pl.get("ipc_socket", cfg.player.ipc_socket),
hwdec=pl.get("hwdec", cfg.player.hwdec),
vo=pl.get("vo", cfg.player.vo),
fullscreen=pl.get("fullscreen", cfg.player.fullscreen),
audio_device=pl.get("audio_device", cfg.player.audio_device),
extra_args=pl.get("extra_args", cfg.player.extra_args),
)
if "kodi_rpc" in data:
kr = data["kodi_rpc"]
cfg.kodi_rpc = KodiRpcConfig(
enabled=kr.get("enabled", cfg.kodi_rpc.enabled),
http_port=kr.get("http_port", cfg.kodi_rpc.http_port),
ws_port=kr.get("ws_port", cfg.kodi_rpc.ws_port),
)
if "web_api" in data:
wa = data["web_api"]
cfg.web_api = WebApiConfig(
enabled=wa.get("enabled", cfg.web_api.enabled),
host=wa.get("host", cfg.web_api.host),
port=wa.get("port", cfg.web_api.port),
)
if "youtube" in data:
yt = data["youtube"]
cfg.youtube = YoutubeConfig(
max_height=yt.get("max_height", cfg.youtube.max_height),
fetch_metadata=yt.get("fetch_metadata", cfg.youtube.fetch_metadata),
)
# Environment variable overrides (useful for Docker & CI)
if jf_url := os.environ.get("JELLYFIN_SERVER_URL"):
cfg.jellyfin.server_url = jf_url.rstrip("/")
if jf_user := os.environ.get("JELLYFIN_USERNAME"):
cfg.jellyfin.username = jf_user
if jf_pass := os.environ.get("JELLYFIN_PASSWORD"):
cfg.jellyfin.password = jf_pass
if jf_dev := os.environ.get("JELLYFIN_DEVICE_NAME"):
cfg.jellyfin.device_name = jf_dev
if mpv_vo := os.environ.get("MPV_VO"):
cfg.player.vo = mpv_vo
if mpv_hwdec := os.environ.get("MPV_HWDEC"):
cfg.player.hwdec = mpv_hwdec
if mpv_fs := os.environ.get("MPV_FULLSCREEN"):
cfg.player.fullscreen = mpv_fs.lower() in ("1", "true", "yes")
if mpv_ao := os.environ.get("MPV_AO"):
cfg.player.audio_device = mpv_ao
return cfg