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%
  • Dockerfile 1%
Find a file
Ralf Warmuth 068b277349
All checks were successful
Test and Build ntfy-downloader Image / build-image (push) Successful in 9s
feat: markiere Clip-Ende aus dem Player
2026-07-16 18:27:29 +02:00
.forgejo/workflows release: bereite V1.1 vor 2026-07-16 15:47:17 +02:00
ntfy_downloader feat: markiere Clip-Ende aus dem Player 2026-07-16 18:27:29 +02:00
releases release: bereite V1.1 vor 2026-07-16 15:47:17 +02:00
tests feat: markiere Clip-Ende aus dem Player 2026-07-16 18:27:29 +02:00
.dockerignore feat: veroeffentliche Registry-Image fuer Compose 2026-07-16 15:19:25 +02:00
.env.example feat: erweitere Web-Galerie um Verwaltung und Clips 2026-07-16 16:27:29 +02:00
.gitignore feat: veroeffentliche Registry-Image fuer Compose 2026-07-16 15:19:25 +02:00
compose.image.yaml feat: erweitere Web-Galerie um Verwaltung und Clips 2026-07-16 16:27:29 +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: erweitere Web-Galerie um Verwaltung und Clips 2026-07-16 16:27:29 +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
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 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

down entfernt die persistenten Named Volumes nicht. Jobdatenbank, Thumbnail-Cache und Video-Tags bleiben deshalb erhalten.

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.
  • 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.
  • 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.