Ein kleiner Python-Dienst, der auf einem ntfy-Kanal lauscht. Sobald eine Nachricht mit einer URL eintrifft, wird diese mit yt-dlp heruntergeladen.
  • Python 99.4%
  • Dockerfile 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ralf Warmuth e9852fa53a
All checks were successful
Test and Build ntfy-downloader Image / build-image (push) Successful in 32s
fix: initialisiere Schnittsteuerung vor Validierung
2026-08-05 23:14:59 +02:00
.forgejo/workflows docs: verwende Umlaute in Release Notes 2026-07-17 01:06:00 +02:00
ntfy_downloader fix: initialisiere Schnittsteuerung vor Validierung 2026-08-05 23:14:59 +02:00
releases docs: verwende Umlaute in Release Notes 2026-07-17 01:06:00 +02:00
tests fix: initialisiere Schnittsteuerung vor Validierung 2026-08-05 23:14:59 +02:00
.dockerignore feat: veroeffentliche Registry-Image fuer Compose 2026-07-16 15:19:25 +02:00
.env.example feat: WhatsApp-kompatible Videoausschnitte exportieren 2026-07-17 00:26:25 +02:00
.gitignore feat: veroeffentliche Registry-Image fuer Compose 2026-07-16 15:19:25 +02:00
compose.image.yaml fix: verwende bestehende Docker-Volumes extern 2026-07-17 00:53:20 +02:00
docker-compose.yml feat: erweitere Web-Galerie um Verwaltung und Clips 2026-07-16 16:27:29 +02:00
Dockerfile feat: erweitere Web-Galerie um Verwaltung und Clips 2026-07-16 16:27:29 +02:00
README.md feat: verbessere Video-Schnittoberfläche 2026-08-05 22:49:14 +02:00
requirements.txt added gif gallery and pornkai downloader 2026-05-29 13:21:02 +02:00

ntfy yt-dlp Downloader

Ein kleiner Python-Dienst, der auf einem ntfy-Kanal lauscht. Sobald eine Nachricht mit einer URL eintrifft, wird diese mit yt-dlp heruntergeladen. Erfolg und Fehler werden als Nachricht auf denselben Kanal zurueckgemeldet. Der Zugriff auf den ntfy-Server erfolgt mit HTTP Basic Auth (Username/Passwort).

Funktionsweise

ntfy Topic  --(JSON-Stream, Basic Auth)-->  Listener
Listener    --(URL erkannt)-->              yt-dlp  -->  DOWNLOAD_DIR
Listener    --(POST Statusmeldung)-->       ntfy Topic
  • Abonniert GET <NTFY_BASE_URL>/<NTFY_TOPIC>/json und verarbeitet message-Events.
  • Extrahiert die erste http(s)://-URL aus dem Nachrichtentext.
  • Laedt sie per yt-dlp ins konfigurierte Verzeichnis.
  • Speichert Auftraege vor dem Download in einer persistenten SQLite-Warteschlange.
  • Setzt einen unterbrochenen ntfy-Stream am letzten gespeicherten Zeitpunkt fort und dedupliziert wiederholte Nachrichten anhand ihrer ntfy-ID.
  • Loest pornkai.com-Links automatisch auf die eingebettete Video-URL (iframe) auf, bevor yt-dlp startet.
  • Meldet Download gestartet (optional), Download erfolgreich oder Download fehlgeschlagen zurueck. Bei Erfolg optional ein Link zur Web-Galerie (/watch?f=...), wenn WEBUI_PUBLIC_URL gesetzt ist.
  • Ignoriert seine eigenen Statusmeldungen (verhindert Rueckkopplungsschleifen).
  • Laeuft dauerhaft weiter: Ein fehlgeschlagener Download wird gemeldet, der Dienst lauscht danach aber normal weiter (es wird nichts erneut versucht).
  • Verbindet bei Stream-Abbruch automatisch mit Backoff neu. Nur bei falschen Zugangsdaten (401/403) wird der Dienst beendet.

Konfiguration

Alle Einstellungen erfolgen ueber Umgebungsvariablen (bzw. .env). Kopiere .env.example nach .env und passe die Werte an:

cp .env.example .env
Variable Pflicht Beschreibung
NTFY_BASE_URL ja URL des ntfy-Servers, z.B. https://ntfy.example.com
NTFY_TOPIC ja Kanal/Topic zum Lauschen und Zurueckmelden
NTFY_USERNAME ja Benutzername (Basic Auth)
NTFY_PASSWORD ja Passwort (Basic Auth)
DOWNLOAD_DIR nein Zielverzeichnis der Downloads. Bei Docker der Host-Pfad, der nach /downloads gemappt wird (z.B. ein SMB-Mount /mnt/smb/XXX); ohne Docker der direkte Pfad. Default ./downloads
JOB_DATABASE nein SQLite-Datei der persistenten Jobqueue. Default ./state/jobs.sqlite3; Docker verwendet /state/jobs.sqlite3
YTDLP_FORMAT nein yt-dlp Format-String, leer = Default
YTDLP_OUTPUT_TEMPLATE nein Ausgabe-Template (Default %(title).120B [%(id)s].%(ext)s; begrenzt lange Dateinamen)
YTDLP_IMPERSONATE nein Browser-TLS-Fingerprint fuer yt-dlp --impersonate (z.B. chrome, firefox). Umgeht TLS-Fingerprinting (z.B. Pornhub). Leer = aus. Default chrome
NOTIFY_ON_START nein true/false, Startmeldung senden (Default true)
DOWNLOAD_TIMEOUT nein Max. Laufzeit pro Download in Sekunden (Default 3600)
LOG_LEVEL nein DEBUG/INFO/WARNING/ERROR (Default INFO)
WEBUI_USERNAME fuer Web-UI Login-Benutzer der Galerie (Basic Auth)
WEBUI_PASSWORD fuer Web-UI Login-Passwort der Galerie (Basic Auth)
WEBUI_PORT nein Host-Port der Galerie (Default 2310)
WEBUI_PUBLIC_URL nein Oeffentliche Basis-URL der Galerie fuer Links in Erfolgsmeldungen (z.B. http://server:2310). Leer = kein Link
WEBUI_DEFAULT_TAG nein Zeigt beim Aufruf der Startseite nur Videos mit diesem Tag. Leer = alle Videos
THUMB_MAX_AGE_DAYS nein Entfernt beim Web-UI-Start aeltere Thumbnail-Cachedateien. Default 30, 0 deaktiviert die Bereinigung
CLIPS_SUBDIR nein Unterverzeichnis fuer erzeugte Videoausschnitte. Default clips
GIFS_SUBDIR nein Unterverzeichnis fuer erzeugte GIFs. Default gifs
GIF_TIMEOUT nein Maximale Laufzeit je ffmpeg-Phase eines GIF-Exports in Sekunden. Default 600
WHATSAPP_SUBDIR nein Unterverzeichnis fuer kompatibel codierte WhatsApp-Videos. Default whatsapp
WHATSAPP_TIMEOUT nein Maximale ffmpeg-Laufzeit fuer einen WhatsApp-Export in Sekunden. Default 120
WHATSAPP_SHARE_TTL nein Gueltigkeit signierter oeffentlicher WhatsApp-Links in Sekunden. Default 604800 (7 Tage)
CLIP_MAX_DURATION nein Maximale Laenge eines Ausschnitts in Sekunden. Default 600
CLIP_TIMEOUT nein Maximale ffmpeg-Laufzeit fuer einen Ausschnitt in Sekunden. Default 120

Lokaler Start mit Docker Build

Diese Variante baut das Image direkt aus dem Checkout und eignet sich fuer Entwicklung und lokale Tests:

cp .env.example .env   # Werte anpassen
mkdir -p ./downloads   # oder vorhandenen DOWNLOAD_DIR-Pfad vorbereiten
docker compose up -d --build
docker compose logs -f

Die heruntergeladenen Dateien landen im Host-Verzeichnis, das in DOWNLOAD_DIR (in der .env) steht, und das nach /downloads im Container gemappt wird. Ist DOWNLOAD_DIR nicht gesetzt, wird ./downloads verwendet. Beispiel fuer einen SMB-Mount:

DOWNLOAD_DIR=/mnt/smb/XXX

Der Container laeuft ohne Root-Rechte als UID/GID 1000. Der konfigurierte Host-Pfad muss bereits existieren und fuer diesen Benutzer beschreibbar sein. SQLite-Zustand, Thumbnail-Cache und Web-UI-Metadaten liegen in den Docker-Volumes ntfy-state, ntfy-thumbs und ntfy-metadata. CPU- und Speicherlimits koennen ueber die Variablen in .env.example angepasst werden. Beide Dienste besitzen einen Docker-Healthcheck.

Betrieb mit dem Registry-Image

Forgejo Actions baut bei jedem Push auf main zuerst die Test-Stage und veroeffentlicht danach das Runtime-Image unter:

git.home.schumbi.de/ralf/ntfy-downloader

Es werden die Tags latest und der vollstaendige Commit-SHA veroeffentlicht. Fuer einen reproduzierbaren Betrieb auf dem Host sollte ein Commit-SHA statt latest verwendet werden.

Auf dem Zielhost werden nur diese Dinge benoetigt:

  • compose.image.yaml
  • eine ausgefuellte .env
  • das in DOWNLOAD_DIR angegebene, fuer die konfigurierte Downloader-UID/GID beschreibbare Verzeichnis

Der Quellcode, die Tests und der Dockerfile werden auf dem Host nicht benoetigt. Falls das Downloadverzeichnis einer anderen numerischen Identitaet gehoert, koennen DOWNLOADER_UID und DOWNLOADER_GID in .env passend gesetzt werden. Downloader und Web-UI verwenden beide diese Identitaet. Bereits vorhandene Volumes fuer Jobzustand und Thumbnails muessen bei einer abweichenden UID einmalig passende Eigentumsrechte erhalten. Das neue Metadaten-Volume wird beim ersten Start automatisch vorbereitet. Beispiel fuer UID/GID 1000:1000:

docker run --rm --user 0:0 \
  -v ntfy-downloader_ntfy-thumbs:/thumbs \
  git.home.schumbi.de/ralf/ntfy-downloader:<TAG> \
  chown -R 1000:1000 /thumbs

Einmalige Einrichtung:

mkdir -p /opt/ntfy-downloader
cd /opt/ntfy-downloader
# compose.image.yaml und eine angepasste .env hier ablegen
mkdir -p ./downloads  # falls DOWNLOAD_DIR=./downloads verwendet wird
docker volume create ntfy-downloader_ntfy-state
docker volume create ntfy-downloader_ntfy-thumbs
docker login git.home.schumbi.de
docker compose -f compose.image.yaml config -q
docker compose -f compose.image.yaml pull
docker compose -f compose.image.yaml up -d
docker compose -f compose.image.yaml ps

Falls die Registry fuer Downloads ohne Anmeldung freigegeben ist, kann docker login entfallen. Status und Logs:

docker compose -f compose.image.yaml ps
docker compose -f compose.image.yaml logs -f

Fuer ein reproduzierbares Update zuerst NTFY_DOWNLOADER_IMAGE_TAG in .env auf den neuen vollstaendigen Commit-SHA setzen und danach ausfuehren:

docker compose -f compose.image.yaml pull
docker compose -f compose.image.yaml up -d
docker compose -f compose.image.yaml ps

Zum Stoppen beziehungsweise Entfernen der Container:

docker compose -f compose.image.yaml stop
docker compose -f compose.image.yaml down

ntfy-state und ntfy-thumbs sind als externe Volumes deklariert und bleiben deshalb auch bei down -v erhalten. Das von Compose verwaltete ntfy-metadata bleibt bei down erhalten, wird mit down -v jedoch geloescht.

Start ohne Docker

Voraussetzungen: Python 3.12+, ffmpeg im PATH.

python -m venv .venv
. .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt

# Konfiguration als Umgebungsvariablen setzen (oder .env per Tool laden)
export NTFY_BASE_URL=https://ntfy.example.com
export NTFY_TOPIC=downloads
export NTFY_USERNAME=meinuser
export NTFY_PASSWORD=meinpasswort
export DOWNLOAD_DIR=./downloads

python -m ntfy_downloader.listener

Verwendung / Test

Eine URL auf den Kanal senden (loest einen Download aus):

curl -u meinuser:meinpasswort \
  -d "https://www.youtube.com/watch?v=dQw4w9WgXcQ" \
  https://ntfy.example.com/downloads

Anschliessend erscheinen die Statusmeldungen (Download erfolgreich / Download fehlgeschlagen) auf demselben Kanal, und die Datei liegt im Downloadverzeichnis.

Die automatisierten Unit-Tests laufen mit:

python -m unittest discover -v

Web-UI / Video-Galerie

Das docker compose-Setup startet zusaetzlich den Dienst ntfy-webui: eine kleine Galerie, die die heruntergeladenen Videos mit Thumbnails und einem HTML5-Player im Browser anzeigt. Sie ist im internen Netz unter http://<server>:2310 erreichbar (Port via WEBUI_PORT aenderbar) und per Basic Auth geschuetzt (WEBUI_USERNAME / WEBUI_PASSWORD).

  • Videos werden rekursiv auch aus Unterverzeichnissen angezeigt.
  • Videos koennen mit frei waehlbaren Tags versehen und danach gefiltert werden. WEBUI_DEFAULT_TAG begrenzt die ungefilterte Startseite auf ein bestimmtes Tag; ueber Alle bleiben saemtliche Videos erreichbar.
  • Die Detailseite kann Videos in vorhandene oder neue Unterverzeichnisse verschieben und nach Bestaetigung dauerhaft loeschen. Tags folgen einer verschobenen Datei automatisch.
  • Zeitstempel-Links koennen direkt aus der aktuellen Abspielposition erstellt werden. Ein Start-/Endbereich laesst sich ausserdem per ffmpeg als neue Datei unter CLIPS_SUBDIR extrahieren und anschliessend abspielen oder herunterladen. Die Extraktion verwendet Stream-Copy, ist daher schnell, kann aber je nach Videoformat am naechsten Keyframe beginnen.
  • Zusätzliche Player-Tasten verwenden abhängig von der Videolänge eine sichtbare Sprungweite von 2, 5, 10, 30 oder 60 Sekunden. Der Schnittbereich zeigt die gewählte Dauer an, kann direkt als Vorschau abgespielt und anschließend über getrennte Aktionen exportiert werden. Derselbe markierte Start-/Endbereich kann bis zu einer Länge von 10 Sekunden als optimiertes GIF in GIFS_SUBDIR exportiert werden und erscheint danach in der GIF-Galerie. GIFs werden mit 8 Bildern pro Sekunde in eine Begrenzungsbox von 640 x 480 Pixeln skaliert, wobei das Seitenverhaeltnis erhalten bleibt. Nach dem Export wird das neue GIF direkt in der Lightbox geoeffnet. Wenn WEBUI_PUBLIC_URL und die ntfy-Zugangsdaten gesetzt sind, erscheint im konfigurierten Topic zusaetzlich eine Nachricht mit einem Link auf diese Ansicht. Die Erzeugung laeuft als Hintergrundjob; eine Fortschrittsseite zeigt anhand der von ffmpeg verarbeiteten Videozeit Phase und Prozentwert an, wartet auf das Ergebnis und verhindert dadurch Gateway-Timeouts bei laengeren FFmpeg-Laufzeiten.
  • Derselbe maximal 10 Sekunden lange Bereich kann als WhatsApp-kompatibles MP4 exportiert werden. Das Video wird auf hoechstens 640 x 480 Pixel skaliert und als H.264 Baseline mit yuv420p, AAC-Tonspur und Faststart erzeugt. Danach oeffnet sich eine Vorschau mit direktem Teilen ueber die Web Share API sowie einem Download als Fallback. Die ntfy-Nachricht enthaelt einen signierten, zeitlich begrenzten Link, den WhatsApp ohne Basic-Auth-Anmeldung abrufen kann. Erzeugte WhatsApp-Dateien liegen unter WHATSAPP_SUBDIR, erscheinen in der Galerie und erhalten automatisch den Tag WhatsApp sowie die Tags des Quellvideos. Beim Start werden auch bereits vorhandene Exporte nachgetaggt.
  • Fuer diese Dateioperationen wird das Download-Verzeichnis schreibbar in die Web-UI eingebunden. Alle Aenderungsaktionen verwenden authentifizierte POST-Formulare mit CSRF-Schutz.
  • Thumbnails werden bei Bedarf per ffmpeg erzeugt und im konfigurierten Cacheverzeichnis beziehungsweise im Docker-Volume ntfy-thumbs gespeichert. Um Last- und Speicherspitzen auf kleinen Hosts zu vermeiden, läuft höchstens eine Thumbnail-Erzeugung gleichzeitig. Parallele Anfragen für dieselbe Datei warten auf denselben Auftrag, statt weitere ffmpeg-Prozesse zu starten.
  • Tags werden unabhaengig vom Download-Verzeichnis im Docker-Volume ntfy-metadata gespeichert.
  • .mp4/.webm laufen nativ im Browser; .mkv/.avi lassen sich evtl. nicht direkt abspielen, koennen aber heruntergeladen werden.

Sicherheit

  • Basic Auth verschluesselt die Zugangsdaten nicht. ntfy und Galerie sollten ausserhalb eines vollstaendig vertrauenswuerdigen Netzes nur ueber HTTPS erreichbar sein, beispielsweise hinter einem Reverse Proxy.
  • Das ntfy-Topic ist eine Ausfuehrungsschnittstelle fuer Downloads und muss so konfiguriert sein, dass nur vertrauenswuerdige Benutzer darauf schreiben duerfen.
  • Akzeptierte URLs koennen ausgehende Verbindungen des Containers ausloesen, auch zu internen Netzwerkzielen. Falls das Topic nicht vollstaendig vertrauenswuerdig ist, muss der Container zusaetzlich per Firewall oder separatem Docker-Netz von internen Diensten isoliert werden.
  • Die Beispiel-Zugangsdaten in .env.example muessen vor dem Start ersetzt werden.
  • Der Web-UI-Container besitzt absichtlich Schreibrechte im Download-Verzeichnis. Das verwendete Basic-Auth-Passwort muss deshalb stark sein und die Galerie darf nur ueber HTTPS beziehungsweise ein vertrauenswuerdiges Netz erreichbar sein.
  • Signierte Links unter /shared/whatsapp umgehen Basic Auth bis zum Ablauf von WHATSAPP_SHARE_TTL. Jeder, der einen solchen Link kennt, kann die zugehoerige Datei waehrend dieser Zeit abrufen. Ein Wechsel von WEBUI_PASSWORD macht alle bereits erzeugten Links ungueltig.