Video Downloader
  • HTML 59.3%
  • Python 23.9%
  • CSS 8.4%
  • JavaScript 7.2%
  • Dockerfile 0.7%
  • Other 0.5%
Find a file
fubledde 4608c68c5b
Some checks failed
Container bauen / build (push) Failing after 10m51s
CI: target the ubuntu-latest runner, not docker
Every runner carrying the 'docker' label is offline; the only idle ones are
unraid-runner (ubuntu-latest) and flaming_battenberg (vps). As written the job
would have queued indefinitely instead of failing visibly.

unraid-runner also runs on the Unraid host, so the linux/amd64 build is native
there rather than emulated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:48:52 +02:00
.forgejo/workflows CI: target the ubuntu-latest runner, not docker 2026-08-19 17:48:52 +02:00
app Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
design Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
unraid Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
.dockerignore Add .dockerignore so the build context excludes .venv, scratch and design sources 2026-08-19 17:34:01 +02:00
.env.example Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
.gitignore Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
docker-compose.yml Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
Dockerfile Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
entrypoint.sh Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
LICENSE Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
README.md Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00
requirements.txt Initial commit: YouTubby, a German YouTube downloader for the iPad 2026-08-19 17:26:23 +02:00

YouTubby

Link einfügen, Qualität wählen, Datei aufs iPad laden. Läuft als Docker-Container auf Unraid, speichert nichts dauerhaft und redet Deutsch.

A small, single-user YouTube downloader with a German interface, built to be used on an iPad. Runs as a Docker container on Unraid.

Port 8488 linux/amd64

Was es tut

Sie fügt einen YouTube-Link ein. YouTubby schaut sich das Video an und bietet an, was es tatsächlich gibt: 1080p bis 360p als Video, dazu MP3 oder M4A für den Ton allein. Sie tippt auf eine Karte, wartet kurz, und speichert die Datei.

Dazwischen liegt kein Konto, kein Passwort und keine Mediathek. Fertige Dateien werden nach sechs Stunden gelöscht, verwaiste Ordner beim nächsten Start.

Installation auf Unraid

Unraid hat das Feld für Template-URLs in Version 6.10 entfernt, deshalb geht es über die Konsole. Einmalig, danach läuft alles über die Oberfläche.

wget -O /boot/config/plugins/dockerMan/templates-user/youtubby.xml https://git.flamingbattenberg.de/fubledde/youtubby/raw/branch/main/unraid/youtubby.xml

Danach Docker → Add Container, oben unter [ User templates ] youtubby wählen. Alle Felder sind vorbelegt. Ein Wert lohnt einen zweiten Blick:

Arbeitsverzeichnis gehört auf den Cache-Pool, nicht auf das Array und nicht auf /mnt/user. Während eines Downloads liegen dort kurzzeitig etwa doppelt so viele Daten wie die fertige Datei groß ist, weil Bild und Ton getrennt geladen und danach zusammengefügt werden.

Eine RAM-Disk wäre bei 64 GB verlockend, ist aber die schlechtere Wahl: Unraids eigenes / liegt bereits im RAM, ein vollgelaufenes /tmp kann den Server mitnehmen, und ein Neuversuch nach einem Verbindungsabbruch ist auf einer RAM-Disk nicht möglich.

Alternativ mit dem Compose-Plugin:

docker compose up -d

Warum es so gebaut ist

Vier Entscheidungen sind nicht Geschmackssache, sondern Folge dessen, wie iPads und YouTube sich tatsächlich verhalten.

Die Datei wird als normaler Link ausgeliefert, nie als Blob. Eine Seite in iOS-Safari darf ungefähr 100 bis 200 MB Speicher belegen. Ein 500-MB-Video über fetch() in einen Blob zu holen killt den Tab. Also: gewöhnliche Navigation zu einer stabilen Server-URL mit Content-Disposition: attachment.

Der Fortschritt wird gepollt, nicht nur gestreamt. Seit iOS 18 meldet ein EventSource, der während des Ruhezustands gestorben ist, weiterhin readyState: OPEN und wirft nie einen Fehler. Wer sich darauf verlässt, zeigt für immer einen eingefrorenen Balken. GET /api/jobs/{id} ist die Wahrheit, der SSE-Stream ist Komfort, und jede Rückkehr in den Tab fragt neu nach.

Der Codec steckt im Format-Selektor, nicht in merge_output_format. Letzteres sortiert nur Container-Vorlieben. VP9 und Opus landen fröhlich in einer .mp4, die im Safari-Tab läuft und sich in der Dateien-App dann nicht öffnen lässt. Der Selektor verlangt ausdrücklich avc1 und mp4a und fällt erst danach zurück.

yt-dlp ist nirgends festgenagelt. YouTube ändert mehrmals im Jahr etwas, das yt-dlp bricht; zuletzt am 17. August 2026, als der bis dahin voreingestellte Client auf jedes Format mit HTTP 403 antwortete. Der Container zieht bei jedem Start die aktuelle Nightly. Geht trotzdem etwas kaputt, ist der Knopf yt-dlp aktualisieren in der Fußzeile die ganze Reparatur.

Deno steckt aus demselben Grund im Image. yt-dlp hat seinen eingebauten JavaScript-Interpreter für YouTube abgegeben; fehlt eine Laufzeit, schreibt es eine Warnung ins Log und liefert danach still nur noch niedrige Auflösungen.

Selbsttest

Ob iOS-Safari eine .mp4 wirklich speichert oder nur eine Vorschau zeigt, ist je nach Version unterschiedlich beantwortet worden. /selbsttest bietet dieselbe Datei in vier Kopfzeilen-Varianten an. Einmal auf dem iPad durchtippen beantwortet die Frage.

Speichert Safari die Varianten 1 und 3 sauber, kann DELIVERY_MODE auf native gestellt werden. Sonst bleibt es bei octet, der Voreinstellung.

Einstellungen

Variable Standard Bedeutung
PORT 8488 Port der Weboberfläche
SCRATCH_DIR /scratch Arbeitsverzeichnis für laufende Downloads
RETENTION_MINUTES 360 Wie lange eine fertige Datei liegen bleibt
MAX_HEIGHT 1080 Höchste angebotene Auflösung
MAX_DURATION_MINUTES 180 Längere Videos werden abgelehnt, 0 schaltet es ab
DELIVERY_MODE octet octet oder native, siehe Selbsttest
YTDL_UPDATE_ON_START true Nightly beim Start ziehen
MAX_CONCURRENT 1 Gleichzeitige Downloads
PUID / PGID 99 / 100 Unraid-Standardbenutzer

Sicherheit

Kein Login, absichtlich. Der Container gehört ins Heimnetz und nicht ins Internet.

Ein Loch bleibt auch dort: jede beliebige Webseite, die im selben Netz geöffnet ist, kann eine Anfrage an diese App schicken. Antworten kann sie nicht lesen, aber Downloads auslösen schon. Deshalb verlangt jeder verändernde Endpunkt einen eigenen Header, den ein fremdes Formular nicht setzen kann. Das kostet in der Bedienung nichts und schließt die Lücke.

Ausserdem: nur http- und https-Links, nur YouTube-Hosts, keine Adressen ins eigene Netz, und enable_file_urls, --exec sowie externe Downloader sind nirgends eingeschaltet.

Soll die App von unterwegs erreichbar sein, gehört davor ein Tailscale- oder WireGuard-Tunnel, keine Portfreigabe.

Was nicht geht

Gekaufte oder geliehene Filme sind kopiergeschützt und lassen sich nicht laden. Altersbeschränkte, private und Mitglieder-Videos brauchen eine Anmeldung bei YouTube, die hier bewusst nicht eingebaut ist. Live-Streams werden abgelehnt, solange sie laufen. Playlists werden auf das einzelne Video reduziert, auch wenn der Link aus der Teilen-Funktion ein &list= mitbringt.

Über 1080p liefert YouTube nur noch VP9 und AV1. AV1 braucht auf Apple-Geräten Hardware-Dekodierung, die ältere iPads nicht haben, und WebM spielt zwar im Safari-Tab, nicht aber in der Dateien-App. Deshalb endet die Auswahl bei 1080p.

Entwicklung

python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt && pip install -U --pre "yt-dlp[default]"
SCRATCH_DIR=./scratch python -m app

Braucht ffmpeg und deno im Pfad.

Der Ordner design/ enthält die Design-Vorlagen, aus denen die Oberfläche gebaut ist: fünf Bildschirme, das Maskottchen in fünf Zuständen und die Farb- und Schriftwerte.

Lizenz

MIT, siehe LICENSE.