A DVR for YouTube subscriptions, laid out so Jellyfin presents each channel as a show and each video as an episode. Cron-driven, idempotent, with a public admin UI for a non-operator. Verified end to end on susan against three real channels: PO tokens, h264 downloads, Jellyfin resolution from local NFOs with all providers disabled, retention and tombstones. Corrections to the original design handover (specs.md documents each with the evidence, and specs.handover-original.md preserves the original): - The format sort selected 360p. Ranking acodec above res makes `bv*` prefer the combined 360p stream, which carries AAC, over the 720p video-only stream whose acodec is none. vcodec now leads, so a video without h264 at 720p yields h264 lower down rather than VP9 this hardware cannot transcode. - yt-dlp now requires a JS runtime and the yt-dlp-ejs solver scripts, which only ship with the [default] extra. Without them the n challenge fails and the mweb formats disappear entirely. - --flat-playlist carries no upload dates, so the specced client-side date filter for backfill was impossible. Backfill is RSS-first. - skipped_old was terminal, so raising a channel's retention appeared to do nothing. Added an explicit rescan. - is_upcoming premieres now defer and retry instead of being skipped forever. - TubeArchivist is gone, so the media root and the tube.jihakuz.xyz vhost were both reclaimed; the latter still pointed at its dead port. 240 offline tests, no network and no real yt-dlp invocation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
34 lines
1.0 KiB
Python
34 lines
1.0 KiB
Python
"""Filesystem paths and process-level constants.
|
|
|
|
Every path is overridable through the environment so the test suite can point the
|
|
whole application at a tmpdir without touching the real media tree.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def _path(env: str, default: str) -> Path:
|
|
return Path(os.environ.get(env, default))
|
|
|
|
|
|
STATE_DIR = _path("YTA_STATE_DIR", "/var/lib/youtube-automate")
|
|
MEDIA_ROOT = _path("YTA_MEDIA_ROOT", "/disks/Plex/YouTube")
|
|
|
|
DB_PATH = _path("YTA_DB_PATH", str(STATE_DIR / "subs.db"))
|
|
LOCK_PATH = _path("YTA_LOCK_PATH", str(STATE_DIR / "run.lock"))
|
|
VENV_BIN = _path("YTA_VENV_BIN", str(STATE_DIR / "venv" / "bin"))
|
|
|
|
WORK_DIR = MEDIA_ROOT / ".work"
|
|
|
|
# Files created by the service must stay group-readable by `mediaserver`, which is
|
|
# how Jellyfin reaches the tree. See specs.md §2.
|
|
UMASK = 0o002
|
|
|
|
# Media-adjacent sidecars we own and therefore may delete on reap.
|
|
SIDECAR_SUFFIXES = (".nfo", "-thumb.jpg", ".en.srt", ".info.json")
|
|
|
|
USER_AGENT = "youtube-automate/1.0"
|