114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""Configuration loading and defaults."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tomllib
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
DEFAULT_CONFIG_NAME = "yt_downloader.toml"
|
|
QUALITY_PRESETS = ("best", "1080", "720", "480", "audio")
|
|
|
|
|
|
def default_config_path() -> Path:
|
|
xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
base = Path(xdg) if xdg else Path.home() / ".config"
|
|
return base / "yt-downloader" / DEFAULT_CONFIG_NAME
|
|
|
|
|
|
def _as_bool(value: Any, default: bool) -> bool:
|
|
if value is None:
|
|
return default
|
|
if isinstance(value, bool):
|
|
return value
|
|
return str(value).strip().lower() in ("1", "true", "yes", "on")
|
|
|
|
|
|
class Config:
|
|
"""Thin wrapper around the parsed TOML config."""
|
|
|
|
def __init__(self, data: dict, path: Path):
|
|
self._data = data
|
|
self.path = path
|
|
|
|
# -- download --------------------------------------------------------
|
|
@property
|
|
def out_dir(self) -> Path:
|
|
return Path(self._data.get("download", {}).get("out", "./videos"))
|
|
|
|
@property
|
|
def quality(self) -> str:
|
|
q = str(self._data.get("download", {}).get("quality", "best")).lower()
|
|
return q if q in QUALITY_PRESETS else "best"
|
|
|
|
@property
|
|
def format_override(self) -> Optional[str]:
|
|
return self._data.get("download", {}).get("format")
|
|
|
|
@property
|
|
def cookies_file(self) -> Optional[str]:
|
|
return self._data.get("download", {}).get("cookies_file")
|
|
|
|
@property
|
|
def cookies_from_browser(self) -> Optional[str]:
|
|
return self._data.get("download", {}).get("cookies_from_browser")
|
|
|
|
# -- vpn ------------------------------------------------------------
|
|
@property
|
|
def vpn_wireguard(self) -> Optional[str]:
|
|
return self._data.get("vpn", {}).get("wireguard")
|
|
|
|
@property
|
|
def vpn_namespace(self) -> Optional[str]:
|
|
return self._data.get("vpn", {}).get("namespace")
|
|
|
|
@property
|
|
def vpn_interface(self) -> Optional[str]:
|
|
return self._data.get("vpn", {}).get("interface")
|
|
|
|
# -- misc -----------------------------------------------------------
|
|
@property
|
|
def dry_run(self) -> bool:
|
|
return _as_bool(self._data.get("global", {}).get("dry_run", False), False)
|
|
|
|
|
|
def load_config(path: Optional[Path | str] = None) -> Config:
|
|
if path is None:
|
|
path = default_config_path()
|
|
path = Path(path)
|
|
if not path.exists():
|
|
raise FileNotFoundError(
|
|
f"Config not found at {path}. Run 'yt-downloader init' first."
|
|
)
|
|
with open(path, "rb") as fh:
|
|
data = tomllib.load(fh)
|
|
return Config(data, path)
|
|
|
|
|
|
def write_default_config(path: Path) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
template = """\
|
|
# yt-downloader configuration
|
|
|
|
[global]
|
|
dry_run = false # when true: plan only, touch nothing
|
|
|
|
[download]
|
|
out = "~/Videos/yt-downloader" # where videos land
|
|
quality = "best" # best | 1080 | 720 | 480 | audio
|
|
# format = "" # raw yt-dlp -f string, overrides quality
|
|
# cookies_file = "/path/to/cookies.txt" # pass your browser cookies (best
|
|
# cookies_from_browser = "firefox" # protection against bot checks;
|
|
# # e.g. chromium, firefox, safari)
|
|
|
|
[vpn]
|
|
# wireguard = "~/.config/yt-downloader/mullvad.conf"
|
|
# # optional: path to a WireGuard (Mullvad) .conf.
|
|
# # When set, `download` runs inside a dedicated
|
|
# # network namespace so ONLY yt-dlp traffic goes
|
|
# # through the VPN. Requires sudo + wireguard-tools.
|
|
# namespace = "mullvad" # optional: network namespace name
|
|
# interface = "mv0" # optional: WireGuard interface name
|
|
"""
|
|
path.write_text(template) |