Keine Beschreibung

ArtyomV2X 470015504e Update README vor 1 Monat
systemd 8727fd6a91 Fix nesting issue that interferes with install one-liner vor 1 Monat
web c6c1742f04 Fix re-declaration of poll timer causing UX to fail vor 1 Monat
README.md 470015504e Update README vor 1 Monat
proxmox-mount.md 8727fd6a91 Fix nesting issue that interferes with install one-liner vor 1 Monat
setup.sh 298fd4891c Setup fixes to pull necessary files vor 1 Monat
update.sh 312bedf1f6 Fix the updater to only pull necessary files vor 1 Monat

README.md

YAAR — YouTube Auto-Archiver and Retagger

Self-hosted YouTube archival system for Proxmox LXC (Alpine Linux). Downloads YouTube content as H.264/AAC MKV video or audio-only files with embedded metadata, written directly into a Jellyfin-compatible folder structure.

Repository: https://gogs.av2x.dev/av2x/yaar


What it does

  • Web UI with a job queue — paste a URL and it auto-fetches title, channel, and upload date
  • Archives as one of three types:
    • TV series episodeShows/<Series>/Season NN/SNNENN - <Episode>.mkv
    • MovieMovies/<Title> (Year).mkv (flat — no per-movie folder)
    • MusicMusic/<Artist>/<Album>/<Track>.<ext> (album optional)
  • Video downloads prefer H.264 + AAC for Jellyfin direct-play (no transcoding), falling back to best available if only VP9/AV1 exists, then remuxing to MKV
  • Music downloads to a selectable audio format (m4a, opus, mp3, flac, aac, wav)
  • Embeds metadata (title, channel/artist, date, URL, thumbnail, chapters) and writes NFO sidecars for Jellyfin scraping of series and movies
  • Live job queue with progress bars, speed/ETA, retry, and error reporting
  • Jobs persist across restarts; queue survives service and container reboots
  • Discrete per-event logging (boot, mount checks, per-job archive logs)
  • Weekly automatic yt-dlp update via cron, plus a one-command app updater

Requirements

  • Proxmox VE 7+ with LXC support
  • An Alpine Linux 3.20 LXC container
  • A media directory on the host (your Jellyfin library) to bind-mount into the container at /nas/jellyfin, containing Movies/, Shows/, and Music/

Quick start

YAAR installs entirely inside your Alpine LXC container. The only host-side step is attaching the Jellyfin bind-mount.

Step 1 — Attach the media bind-mount (Proxmox host, one-time)

See proxmox-mount.md for the full guide. In short, on the Proxmox host shell:

pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin
pct restart <CTID>

Replace /mnt/nas/jellyfin with wherever your media lives on the host. The mp=/nas/jellyfin target must stay exactly as-is — YAAR expects that path.

Or via the web UI: Container → Resources → Add → Mount Point, host path /mnt/nas/jellyfin, container path /nas/jellyfin, read-only off.

Step 2 — Install (inside the container)

One line, run as root inside the container:

curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/setup.sh | sh

Or trigger it from the Proxmox host without entering the container:

pct exec <CTID> -- sh -c 'curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/setup.sh | sh'

setup.sh installs Python, ffmpeg, yt-dlp, Flask and Gunicorn, the app files, an OpenRC service, the weekly yt-dlp auto-update cron, and the yaar-update command. It refuses to run on the Proxmox host and fetches any files it needs directly from the repo.

When it finishes, open http://<container-ip>:7474.


Updating

Pull the latest code, redeploy, and restart — with automatic backup and rollback if the new code fails to start:

yaar-update              # update app + restart service
yaar-update --deps       # also upgrade yt-dlp / Flask / Gunicorn
yaar-update --reboot     # reboot the whole container after updating

Or the self-contained one-liner (always fetches the newest update logic too):

curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/update.sh | sh

The updater downloads both app files to a temp location and only deploys if both succeed, backs up the previous versions to /opt/yaar/backups/ (keeping the last 5), and restarts the service. If the service fails its health check on the new code, it automatically restores the previous version so YAAR is never left down.


Service management (Alpine / OpenRC)

rc-service yaar start
rc-service yaar stop
rc-service yaar restart
rc-service yaar status

Jellyfin integration

Point Jellyfin at the library roots inside your mounted media directory:

Library type Path
Movies /nas/jellyfin/Movies
Shows /nas/jellyfin/Shows
Music /nas/jellyfin/Music

For the Shows library, enable NFO metadata reading so episode details from yt-dlp populate correctly. Movies use a flat layout (Movies/<Title> (Year).mkv with a matching .nfo sidecar), which Jellyfin scrapes by filename.


Format choices

Video (series / movie):

Setting Value Why
Video codec H.264 (avc1) Universal Jellyfin direct-play, no transcoding
Audio codec AAC (mp4a) Universal, no transcoding on any client
Container MKV Supports metadata, chapters, thumbnails
Selector bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/…/best H.264+AAC first, graceful fallback

If a video is only offered as VP9/AV1, yt-dlp falls back to the best available stream and still remuxes to MKV. Jellyfin may transcode those on weaker clients — a YouTube-side limitation, not a YAAR bug.

Music: selectable per job — m4a (AAC, default), opus, mp3, flac, aac, or wav. Note that YouTube source audio is itself lossy (AAC/Opus), so flac/wav won't add quality, only file size.


File & folder layout

/opt/yaar/
  web/
    app.py                ← Flask application
    templates/index.html  ← Web UI
  venv/                   ← Python virtualenv
  jobs/                   ← Persisted job JSON (queue survives restarts)
  backups/                ← Previous app versions kept by yaar-update
  update.sh               ← Update script (also runs as `yaar-update`)
  logs/
    yaar.log              ← Unified log (everything)
    gunicorn.log          ← Web server log
    access.log            ← HTTP access log
    log_boot/<ts>.log     ← One file per service start
    log_mount/<ts>.log    ← One file per mount check
    log_archive/<id>.log  ← One file per archive job (named by video id)

/nas/jellyfin/            ← Your bind-mounted media (mp0)
  Movies/
    Fantastic Voyage (1966).mkv
    Fantastic Voyage (1966).nfo
  Shows/
    Kurzgesagt/
      Season 01/
        S01E01 - The Egg.mkv
        S01E01 - The Egg.nfo
  Music/
    Nujabes/
      Modal Soul/
        03 - Luv(sic) Part 3.m4a

Configuration

Environment variables (in /etc/yaar.env):

Variable Default Description
YAAR_BASE /opt/yaar App, logs, jobs, backups directory
YAAR_MEDIA /nas/jellyfin Root media output directory (the mp0 mount)
YAAR_PORT 7474 Web UI port

After changing any of these, restart: rc-service yaar restart.


Logs

tail -f /opt/yaar/logs/yaar.log                 # everything, live
ls /opt/yaar/logs/log_archive/                  # per-job archive logs
cat /opt/yaar/logs/log_archive/<videoid>__*.log # a specific job's full trace

Archive logs are named by YouTube video id, so you can find a job's log by URL without knowing its internal id.


Troubleshooting

Queue is frozen / blank, console shows a SyntaxError The served index.html is out of date. Run yaar-update and hard-refresh the browser (Ctrl+Shift+R) to clear the cached script.

Mount banner is red / downloads fail immediately /nas/jellyfin isn't mounted or isn't writable. Confirm the mp0 bind-mount is attached and the container was restarted after adding it:

ls /nas/jellyfin        # should list Movies/ Shows/ Music/

For unprivileged containers with permission issues, map the mount UID/GID: pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin,uid=0,gid=0.

Download fails with "Sign in to confirm your age" Age-restricted videos need cookies. Export cookies from a logged-in browser and add a --cookies reference in build_ydl_opts() in app.py.

Falls back to VP9 instead of H.264 Some videos are only published in VP9/AV1. YAAR still downloads and remuxes; Jellyfin may transcode on weaker clients. YouTube-side limitation.

Port already in use Change YAAR_PORT in /etc/yaar.env and restart the service.


Manual yt-dlp update

The weekly cron handles this automatically, but to force it:

/opt/yaar/venv/bin/pip install --upgrade yt-dlp
rc-service yaar restart

Or just yaar-update --deps, which upgrades yt-dlp, Flask, and Gunicorn together.