Add yt-downloader: single-URL YouTube downloads through an isolated Mullvad WireGuard tunnel
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user